From 0729cc9315a71c28af14dc56e384ea2b8a85c7d0 Mon Sep 17 00:00:00 2001 From: kaltokri Date: Fri, 1 Mar 2024 17:25:17 +0100 Subject: [PATCH] SpawnStatic moved to new repo --- .../MOOSE_Spawn_Test.lua | 123253 --------------- .../SPS - 010 - Simple Spawning.lua | 31 - .../SPS - 010 - Simple Spawning.miz | Bin 8220162 -> 0 bytes .../SPS - 050 - Spawn FARPs.lua | 62 - .../SPS - 050 - Spawn FARPs.miz | Bin 4305533 -> 0 bytes 5 files changed, 123346 deletions(-) delete mode 100644 Core/SpawnStatic/SPS - 010 - Simple Spawning/MOOSE_Spawn_Test.lua delete mode 100644 Core/SpawnStatic/SPS - 010 - Simple Spawning/SPS - 010 - Simple Spawning.lua delete mode 100644 Core/SpawnStatic/SPS - 010 - Simple Spawning/SPS - 010 - Simple Spawning.miz delete mode 100644 Core/SpawnStatic/SPS - 050- Spawn FARPs/SPS - 050 - Spawn FARPs.lua delete mode 100644 Core/SpawnStatic/SPS - 050- Spawn FARPs/SPS - 050 - Spawn FARPs.miz diff --git a/Core/SpawnStatic/SPS - 010 - Simple Spawning/MOOSE_Spawn_Test.lua b/Core/SpawnStatic/SPS - 010 - Simple Spawning/MOOSE_Spawn_Test.lua deleted file mode 100644 index 11b1f3c302..0000000000 --- a/Core/SpawnStatic/SPS - 010 - Simple Spawning/MOOSE_Spawn_Test.lua +++ /dev/null @@ -1,123253 +0,0 @@ -env.info('*** MOOSE GITHUB Commit Hash ID: 2024-02-04T13:13:06+01:00-4307ddcad379150951e4aa7a4d15aeba6146fa9b ***') -if not MOOSE_DEVELOPMENT_FOLDER then -MOOSE_DEVELOPMENT_FOLDER='Scripts' -end -ModuleLoader=MOOSE_DEVELOPMENT_FOLDER..'/Moose/Modules.lua' -if io then -local f=io.open(ModuleLoader,"r") -if f~=nil then -io.close(f) -env.info('*** MOOSE DYNAMIC INCLUDE START *** ') -local base=_G -__Moose={} -__Moose.Include=function(IncludeFile) -if not __Moose.Includes[IncludeFile]then -__Moose.Includes[IncludeFile]=IncludeFile -local f=assert(base.loadfile(IncludeFile)) -if f==nil then -error("Moose: Could not load Moose file "..IncludeFile) -else -env.info("Moose: "..IncludeFile.." dynamically loaded.") -return f() -end -end -end -__Moose.Includes={} -__Moose.Include(MOOSE_DEVELOPMENT_FOLDER..'/Moose/Modules.lua') -BASE:TraceOnOff(true) -env.info('*** MOOSE INCLUDE END *** ') -do return end -end -else -env.info('*** MOOSE DYNAMIC INCLUDE NOT POSSIBLE (Desanitize io to use it) *** ') -end -env.info('*** MOOSE STATIC INCLUDE START *** ') -ENUMS={} -env.setErrorMessageBoxEnabled(false) -ENUMS.ROE={ -WeaponFree=0, -OpenFireWeaponFree=1, -OpenFire=2, -ReturnFire=3, -WeaponHold=4, -} -ENUMS.ROT={ -NoReaction=0, -PassiveDefense=1, -EvadeFire=2, -BypassAndEscape=3, -AllowAbortMission=4, -} -ENUMS.AlarmState={ -Auto=0, -Green=1, -Red=2, -} -ENUMS.WeaponFlag={ -LGB=2, -TvGB=4, -SNSGB=8, -HEBomb=16, -Penetrator=32, -NapalmBomb=64, -FAEBomb=128, -ClusterBomb=256, -Dispencer=512, -CandleBomb=1024, -ParachuteBomb=2147483648, -LightRocket=2048, -MarkerRocket=4096, -CandleRocket=8192, -HeavyRocket=16384, -AntiRadarMissile=32768, -AntiShipMissile=65536, -AntiTankMissile=131072, -FireAndForgetASM=262144, -LaserASM=524288, -TeleASM=1048576, -CruiseMissile=2097152, -AntiRadarMissile2=1073741824, -SRAM=4194304, -MRAAM=8388608, -LRAAM=16777216, -IR_AAM=33554432, -SAR_AAM=67108864, -AR_AAM=134217728, -GunPod=268435456, -BuiltInCannon=536870912, -GuidedBomb=14, -AnyUnguidedBomb=2147485680, -AnyBomb=2147485694, -AnyRocket=30720, -GuidedASM=1572864, -TacticalASM=1835008, -AnyASM=4161536, -AnyASM2=1077903360, -AnyAAM=264241152, -AnyAutonomousMissile=36012032, -AnyMissile=268402688, -Cannons=805306368, -Torpedo=4294967296, -Auto=3221225470, -AutoDCS=1073741822, -AnyAG=2956984318, -AnyAA=264241152, -AnyUnguided=2952822768, -AnyGuided=268402702, -} -ENUMS.WeaponType={} -ENUMS.WeaponType.Bomb={ -LGB=2, -TvGB=4, -SNSGB=8, -HEBomb=16, -Penetrator=32, -NapalmBomb=64, -FAEBomb=128, -ClusterBomb=256, -Dispencer=512, -CandleBomb=1024, -ParachuteBomb=2147483648, -GuidedBomb=14, -AnyUnguidedBomb=2147485680, -AnyBomb=2147485694, -} -ENUMS.WeaponType.Rocket={ -LightRocket=2048, -MarkerRocket=4096, -CandleRocket=8192, -HeavyRocket=16384, -AnyRocket=30720, -} -ENUMS.WeaponType.Gun={ -GunPod=268435456, -BuiltInCannon=536870912, -Cannons=805306368, -} -ENUMS.WeaponType.Missile={ -AntiRadarMissile=32768, -AntiShipMissile=65536, -AntiTankMissile=131072, -FireAndForgetASM=262144, -LaserASM=524288, -TeleASM=1048576, -CruiseMissile=2097152, -AntiRadarMissile2=1073741824, -GuidedASM=1572864, -TacticalASM=1835008, -AnyASM=4161536, -AnyASM2=1077903360, -AnyAutonomousMissile=36012032, -AnyMissile=268402688, -} -ENUMS.WeaponType.AAM={ -SRAM=4194304, -MRAAM=8388608, -LRAAM=16777216, -IR_AAM=33554432, -SAR_AAM=67108864, -AR_AAM=134217728, -AnyAAM=264241152, -} -ENUMS.WeaponType.Torpedo={ -Torpedo=4294967296, -} -ENUMS.WeaponType.Any={ -Weapon=3221225470, -AG=2956984318, -AA=264241152, -Unguided=2952822768, -Guided=268402702, -} -ENUMS.MissionTask={ -NOTHING="Nothing", -AFAC="AFAC", -ANTISHIPSTRIKE="Antiship Strike", -AWACS="AWACS", -CAP="CAP", -CAS="CAS", -ESCORT="Escort", -GROUNDESCORT="Ground escort", -FIGHTERSWEEP="Fighter Sweep", -GROUNDATTACK="Ground Attack", -INTERCEPT="Intercept", -PINPOINTSTRIKE="Pinpoint Strike", -RECONNAISSANCE="Reconnaissance", -REFUELING="Refueling", -RUNWAYATTACK="Runway Attack", -SEAD="SEAD", -TRANSPORT="Transport", -} -ENUMS.Formation={} -ENUMS.Formation.FixedWing={} -ENUMS.Formation.FixedWing.LineAbreast={} -ENUMS.Formation.FixedWing.LineAbreast.Close=65537 -ENUMS.Formation.FixedWing.LineAbreast.Open=65538 -ENUMS.Formation.FixedWing.LineAbreast.Group=65539 -ENUMS.Formation.FixedWing.Trail={} -ENUMS.Formation.FixedWing.Trail.Close=131073 -ENUMS.Formation.FixedWing.Trail.Open=131074 -ENUMS.Formation.FixedWing.Trail.Group=131075 -ENUMS.Formation.FixedWing.Wedge={} -ENUMS.Formation.FixedWing.Wedge.Close=196609 -ENUMS.Formation.FixedWing.Wedge.Open=196610 -ENUMS.Formation.FixedWing.Wedge.Group=196611 -ENUMS.Formation.FixedWing.EchelonRight={} -ENUMS.Formation.FixedWing.EchelonRight.Close=262145 -ENUMS.Formation.FixedWing.EchelonRight.Open=262146 -ENUMS.Formation.FixedWing.EchelonRight.Group=262147 -ENUMS.Formation.FixedWing.EchelonLeft={} -ENUMS.Formation.FixedWing.EchelonLeft.Close=327681 -ENUMS.Formation.FixedWing.EchelonLeft.Open=327682 -ENUMS.Formation.FixedWing.EchelonLeft.Group=327683 -ENUMS.Formation.FixedWing.FingerFour={} -ENUMS.Formation.FixedWing.FingerFour.Close=393217 -ENUMS.Formation.FixedWing.FingerFour.Open=393218 -ENUMS.Formation.FixedWing.FingerFour.Group=393219 -ENUMS.Formation.FixedWing.Spread={} -ENUMS.Formation.FixedWing.Spread.Close=458753 -ENUMS.Formation.FixedWing.Spread.Open=458754 -ENUMS.Formation.FixedWing.Spread.Group=458755 -ENUMS.Formation.FixedWing.BomberElement={} -ENUMS.Formation.FixedWing.BomberElement.Close=786433 -ENUMS.Formation.FixedWing.BomberElement.Open=786434 -ENUMS.Formation.FixedWing.BomberElement.Group=786435 -ENUMS.Formation.FixedWing.BomberElementHeight={} -ENUMS.Formation.FixedWing.BomberElementHeight.Close=851968 -ENUMS.Formation.FixedWing.FighterVic={} -ENUMS.Formation.FixedWing.FighterVic.Close=917505 -ENUMS.Formation.FixedWing.FighterVic.Open=917506 -ENUMS.Formation.RotaryWing={} -ENUMS.Formation.RotaryWing.Column={} -ENUMS.Formation.RotaryWing.Column.D70=720896 -ENUMS.Formation.RotaryWing.Wedge={} -ENUMS.Formation.RotaryWing.Wedge.D70=8 -ENUMS.Formation.RotaryWing.FrontRight={} -ENUMS.Formation.RotaryWing.FrontRight.D300=655361 -ENUMS.Formation.RotaryWing.FrontRight.D600=655362 -ENUMS.Formation.RotaryWing.FrontLeft={} -ENUMS.Formation.RotaryWing.FrontLeft.D300=655617 -ENUMS.Formation.RotaryWing.FrontLeft.D600=655618 -ENUMS.Formation.RotaryWing.EchelonRight={} -ENUMS.Formation.RotaryWing.EchelonRight.D70=589825 -ENUMS.Formation.RotaryWing.EchelonRight.D300=589826 -ENUMS.Formation.RotaryWing.EchelonRight.D600=589827 -ENUMS.Formation.RotaryWing.EchelonLeft={} -ENUMS.Formation.RotaryWing.EchelonLeft.D70=590081 -ENUMS.Formation.RotaryWing.EchelonLeft.D300=590082 -ENUMS.Formation.RotaryWing.EchelonLeft.D600=590083 -ENUMS.Formation.Vehicle={} -ENUMS.Formation.Vehicle.Vee="Vee" -ENUMS.Formation.Vehicle.EchelonRight="EchelonR" -ENUMS.Formation.Vehicle.OffRoad="Off Road" -ENUMS.Formation.Vehicle.Rank="Rank" -ENUMS.Formation.Vehicle.EchelonLeft="EchelonL" -ENUMS.Formation.Vehicle.OnRoad="On Road" -ENUMS.Formation.Vehicle.Cone="Cone" -ENUMS.Formation.Vehicle.Diamond="Diamond" -ENUMS.FormationOld={} -ENUMS.FormationOld.FixedWing={} -ENUMS.FormationOld.FixedWing.LineAbreast=1 -ENUMS.FormationOld.FixedWing.Trail=2 -ENUMS.FormationOld.FixedWing.Wedge=3 -ENUMS.FormationOld.FixedWing.EchelonRight=4 -ENUMS.FormationOld.FixedWing.EchelonLeft=5 -ENUMS.FormationOld.FixedWing.FingerFour=6 -ENUMS.FormationOld.FixedWing.SpreadFour=7 -ENUMS.FormationOld.FixedWing.BomberElement=12 -ENUMS.FormationOld.FixedWing.BomberElementHeight=13 -ENUMS.FormationOld.FixedWing.FighterVic=14 -ENUMS.FormationOld.RotaryWing={} -ENUMS.FormationOld.RotaryWing.Wedge=8 -ENUMS.FormationOld.RotaryWing.Echelon=9 -ENUMS.FormationOld.RotaryWing.Front=10 -ENUMS.FormationOld.RotaryWing.Column=11 -ENUMS.Morse={} -ENUMS.Morse.A="* -" -ENUMS.Morse.B="- * * *" -ENUMS.Morse.C="- * - *" -ENUMS.Morse.D="- * *" -ENUMS.Morse.E="*" -ENUMS.Morse.F="* * - *" -ENUMS.Morse.G="- - *" -ENUMS.Morse.H="* * * *" -ENUMS.Morse.I="* *" -ENUMS.Morse.J="* - - -" -ENUMS.Morse.K="- * -" -ENUMS.Morse.L="* - * *" -ENUMS.Morse.M="- -" -ENUMS.Morse.N="- *" -ENUMS.Morse.O="- - -" -ENUMS.Morse.P="* - - *" -ENUMS.Morse.Q="- - * -" -ENUMS.Morse.R="* - *" -ENUMS.Morse.S="* * *" -ENUMS.Morse.T="-" -ENUMS.Morse.U="* * -" -ENUMS.Morse.V="* * * -" -ENUMS.Morse.W="* - -" -ENUMS.Morse.X="- * * -" -ENUMS.Morse.Y="- * - -" -ENUMS.Morse.Z="- - * *" -ENUMS.Morse.N1="* - - - -" -ENUMS.Morse.N2="* * - - -" -ENUMS.Morse.N3="* * * - -" -ENUMS.Morse.N4="* * * * -" -ENUMS.Morse.N5="* * * * *" -ENUMS.Morse.N6="- * * * *" -ENUMS.Morse.N7="- - * * *" -ENUMS.Morse.N8="- - - * *" -ENUMS.Morse.N9="- - - - *" -ENUMS.Morse.N0="- - - - -" -ENUMS.Morse[" "]=" " -ENUMS.ISOLang= -{ -Arabic='AR', -Chinese='ZH', -English='EN', -French='FR', -German='DE', -Russian='RU', -Spanish='ES', -Japanese='JA', -Italian='IT', -} -ENUMS.Phonetic= -{ -A='Alpha', -B='Bravo', -C='Charlie', -D='Delta', -E='Echo', -F='Foxtrot', -G='Golf', -H='Hotel', -I='India', -J='Juliett', -K='Kilo', -L='Lima', -M='Mike', -N='November', -O='Oscar', -P='Papa', -Q='Quebec', -R='Romeo', -S='Sierra', -T='Tango', -U='Uniform', -V='Victor', -W='Whiskey', -X='Xray', -Y='Yankee', -Z='Zulu', -} -ENUMS.ReportingName= -{ -NATO={ -Dragon="JF-17", -Fagot="MiG-15", -Farmer="MiG-19", -Felon="Su-57", -Fencer="Su-24", -Fishbed="MiG-21", -Fitter="Su-17", -Flogger="MiG-23", -Flogger_D="MiG-27", -Flagon="Su-15", -Foxbat="MiG-25", -Fulcrum="MiG-29", -Foxhound="MiG-31", -Flanker="Su-27", -Flanker_C="Su-30", -Flanker_E="Su-35", -Flanker_F="Su-37", -Flanker_L="J-11A", -Firebird="J-10", -Sea_Flanker="Su-33", -Fullback="Su-34", -Frogfoot="Su-25", -Tomcat="F-14", -Mirage="Mirage", -Codling="Yak-40", -Maya="L-39", -Warthog="A-10", -Skyhawk="A-4E", -Viggen="AJS37", -Harrier_B="AV8BNA", -Harrier="AV-8B", -Spirit="B-2", -Aviojet="C-101", -Nighthawk="F-117A", -Eagle="F-15C", -Mudhen="F-15E", -Viper="F-16", -Phantom="F-4E", -Tiger="F-5", -Sabre="F-86", -Hornet="A-18", -Hawk="Hawk", -Albatros="L-39", -Goshawk="T-45", -Starfighter="F-104", -Tornado="Tornado", -Atlas="A400", -Lancer="B1-B", -Stratofortress="B-52H", -Hercules="C-130", -Super_Hercules="Hercules", -Globemaster="C-17", -Greyhound="C-2A", -Galaxy="C-5", -Hawkeye="E-2D", -Sentry="E-3A", -Stratotanker="KC-135", -Gasstation="KC-135MPRS", -Extender="KC-10", -Orion="P-3C", -Viking="S-3B", -Osprey="V-22", -Badger="H6-J", -Bear_J="Tu-142", -Bear="Tu-95", -Blinder="Tu-22", -Blackjack="Tu-160", -Clank="An-30", -Curl="An-26", -Candid="IL-76", -Midas="IL-78", -Mainstay="A-50", -Mainring="KJ-2000", -Yak="Yak-52", -Helix="Ka-27", -Shark="Ka-50", -Hind="Mi-24", -Halo="Mi-26", -Hip="Mi-8", -Havoc="Mi-28", -Gazelle="SA342", -Huey="UH-1H", -Cobra="AH-1", -Apache="AH-64", -Chinook="CH-47", -Sea_Stallion="CH-53", -Kiowa="OH-58", -Seahawk="SH-60", -Blackhawk="UH-60", -Sea_King="S-61", -UCAV="WingLoong", -Reaper="MQ-9", -Predator="MQ-1A", -} -} -ENUMS.Link16Power={ -none=0, -low=1, -medium=2, -high=3, -} -ENUMS.Storage={ -weapons={ -missiles={}, -bombs={}, -nurs={}, -containers={}, -droptanks={}, -adapters={}, -torpedoes={}, -} -} -ENUMS.Storage.weapons.nurs.SNEB_TYPE253_F1B="weapons.nurs.SNEB_TYPE253_F1B" -ENUMS.Storage.weapons.missiles.P_24T="weapons.missiles.P_24T" -ENUMS.Storage.weapons.bombs.BLU_3B_OLD="weapons.bombs.BLU-3B_OLD" -ENUMS.Storage.weapons.missiles.AGM_154="weapons.missiles.AGM_154" -ENUMS.Storage.weapons.nurs.HYDRA_70_M151_M433="weapons.nurs.HYDRA_70_M151_M433" -ENUMS.Storage.weapons.bombs.SAM_Avenger_M1097_Skid_7090lb="weapons.bombs.SAM Avenger M1097 Skid [7090lb]" -ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk5="weapons.bombs.British_GP_250LB_Bomb_Mk5" -ENUMS.Storage.weapons.containers.OV10_SMOKE="weapons.containers.{OV10_SMOKE}" -ENUMS.Storage.weapons.bombs.BLU_4B_OLD="weapons.bombs.BLU-4B_OLD" -ENUMS.Storage.weapons.bombs.FAB_500M54="weapons.bombs.FAB-500M54" -ENUMS.Storage.weapons.bombs.GBU_38="weapons.bombs.GBU_38" -ENUMS.Storage.weapons.containers.F_15E_AXQ_14_DATALINK="weapons.containers.F-15E_AXQ-14_DATALINK" -ENUMS.Storage.weapons.bombs.BEER_BOMB="weapons.bombs.BEER_BOMB" -ENUMS.Storage.weapons.bombs.P_50T="weapons.bombs.P-50T" -ENUMS.Storage.weapons.nurs.C_8CM_GN="weapons.nurs.C_8CM_GN" -ENUMS.Storage.weapons.bombs.FAB_500SL="weapons.bombs.FAB-500SL" -ENUMS.Storage.weapons.bombs.KAB_1500Kr="weapons.bombs.KAB_1500Kr" -ENUMS.Storage.weapons.bombs.two50_2="weapons.bombs.250-2" -ENUMS.Storage.weapons.droptanks.Spitfire_tank_1="weapons.droptanks.Spitfire_tank_1" -ENUMS.Storage.weapons.missiles.AGM_65G="weapons.missiles.AGM_65G" -ENUMS.Storage.weapons.missiles.AGM_65A="weapons.missiles.AGM_65A" -ENUMS.Storage.weapons.containers.Hercules_JATO="weapons.containers.Hercules_JATO" -ENUMS.Storage.weapons.nurs.HYDRA_70_M259="weapons.nurs.HYDRA_70_M259" -ENUMS.Storage.weapons.missiles.AGM_84E="weapons.missiles.AGM_84E" -ENUMS.Storage.weapons.bombs.AN_M30A1="weapons.bombs.AN_M30A1" -ENUMS.Storage.weapons.nurs.C_25="weapons.nurs.C_25" -ENUMS.Storage.weapons.containers.AV8BNA_ALQ164="weapons.containers.AV8BNA_ALQ164" -ENUMS.Storage.weapons.containers.lav_25="weapons.containers.lav-25" -ENUMS.Storage.weapons.missiles.P_60="weapons.missiles.P_60" -ENUMS.Storage.weapons.bombs.FAB_1500="weapons.bombs.FAB_1500" -ENUMS.Storage.weapons.droptanks.FuelTank_350L="weapons.droptanks.FuelTank_350L" -ENUMS.Storage.weapons.bombs.AAA_Vulcan_M163_Skid_21577lb="weapons.bombs.AAA Vulcan M163 Skid [21577lb]" -ENUMS.Storage.weapons.missiles.Kormoran="weapons.missiles.Kormoran" -ENUMS.Storage.weapons.droptanks.HB_F14_EXT_DROPTANK_EMPTY="weapons.droptanks.HB_F14_EXT_DROPTANK_EMPTY" -ENUMS.Storage.weapons.droptanks.FuelTank_150L="weapons.droptanks.FuelTank_150L" -ENUMS.Storage.weapons.missiles.Rb_15F_for_A_I="weapons.missiles.Rb 15F (for A.I.)" -ENUMS.Storage.weapons.missiles.RB75T="weapons.missiles.RB75T" -ENUMS.Storage.weapons.missiles.Vikhr_M="weapons.missiles.Vikhr_M" -ENUMS.Storage.weapons.nurs.FFAR_M156_WP="weapons.nurs.FFAR M156 WP" -ENUMS.Storage.weapons.nurs.British_HE_60LBSAPNo2_3INCHNo1="weapons.nurs.British_HE_60LBSAPNo2_3INCHNo1" -ENUMS.Storage.weapons.missiles.DWS39_MJ2="weapons.missiles.DWS39_MJ2" -ENUMS.Storage.weapons.bombs.HEBOMBD="weapons.bombs.HEBOMBD" -ENUMS.Storage.weapons.missiles.CATM_9M="weapons.missiles.CATM_9M" -ENUMS.Storage.weapons.bombs.Mk_81="weapons.bombs.Mk_81" -ENUMS.Storage.weapons.droptanks.Drop_Tank_300_Liter="weapons.droptanks.Drop_Tank_300_Liter" -ENUMS.Storage.weapons.containers.HMMWV_M1025="weapons.containers.HMMWV_M1025" -ENUMS.Storage.weapons.bombs.SAM_CHAPARRAL_Air_21624lb="weapons.bombs.SAM CHAPARRAL Air [21624lb]" -ENUMS.Storage.weapons.missiles.AGM_154A="weapons.missiles.AGM_154A" -ENUMS.Storage.weapons.bombs.Mk_84AIR_TP="weapons.bombs.Mk_84AIR_TP" -ENUMS.Storage.weapons.bombs.GBU_31_V_3B="weapons.bombs.GBU_31_V_3B" -ENUMS.Storage.weapons.nurs.C_8CM_WH="weapons.nurs.C_8CM_WH" -ENUMS.Storage.weapons.missiles.Matra_Super_530D="weapons.missiles.Matra Super 530D" -ENUMS.Storage.weapons.nurs.ARF8M3TPSM="weapons.nurs.ARF8M3TPSM" -ENUMS.Storage.weapons.missiles.TGM_65H="weapons.missiles.TGM_65H" -ENUMS.Storage.weapons.nurs.M8rocket="weapons.nurs.M8rocket" -ENUMS.Storage.weapons.bombs.GBU_27="weapons.bombs.GBU_27" -ENUMS.Storage.weapons.missiles.AGR_20A="weapons.missiles.AGR_20A" -ENUMS.Storage.weapons.missiles.LS_6_250="weapons.missiles.LS-6-250" -ENUMS.Storage.weapons.droptanks.M2KC_RPL_522_EMPTY="weapons.droptanks.M2KC_RPL_522_EMPTY" -ENUMS.Storage.weapons.droptanks.M2KC_02_RPL541="weapons.droptanks.M2KC_02_RPL541" -ENUMS.Storage.weapons.missiles.AGM_45="weapons.missiles.AGM_45" -ENUMS.Storage.weapons.missiles.AGM_84A="weapons.missiles.AGM_84A" -ENUMS.Storage.weapons.bombs.APC_BTR_80_Air_23936lb="weapons.bombs.APC BTR-80 Air [23936lb]" -ENUMS.Storage.weapons.missiles.P_33E="weapons.missiles.P_33E" -ENUMS.Storage.weapons.missiles.Ataka_9M120="weapons.missiles.Ataka_9M120" -ENUMS.Storage.weapons.bombs.MK76="weapons.bombs.MK76" -ENUMS.Storage.weapons.bombs.AB_250_2_SD_2="weapons.bombs.AB_250_2_SD_2" -ENUMS.Storage.weapons.missiles.Rb_05A="weapons.missiles.Rb 05A" -ENUMS.Storage.weapons.bombs.ART_GVOZDIKA_34720lb="weapons.bombs.ART GVOZDIKA [34720lb]" -ENUMS.Storage.weapons.bombs.Generic_Crate_20000lb="weapons.bombs.Generic Crate [20000lb]" -ENUMS.Storage.weapons.bombs.FAB_100SV="weapons.bombs.FAB_100SV" -ENUMS.Storage.weapons.bombs.BetAB_500="weapons.bombs.BetAB_500" -ENUMS.Storage.weapons.droptanks.M2KC_02_RPL541_EMPTY="weapons.droptanks.M2KC_02_RPL541_EMPTY" -ENUMS.Storage.weapons.droptanks.PTB600_MIG15="weapons.droptanks.PTB600_MIG15" -ENUMS.Storage.weapons.missiles.Rb_24J="weapons.missiles.Rb 24J" -ENUMS.Storage.weapons.nurs.C_8CM_BU="weapons.nurs.C_8CM_BU" -ENUMS.Storage.weapons.nurs.SNEB_TYPE259E_F1B="weapons.nurs.SNEB_TYPE259E_F1B" -ENUMS.Storage.weapons.nurs.WGr21="weapons.nurs.WGr21" -ENUMS.Storage.weapons.bombs.SAMP250HD="weapons.bombs.SAMP250HD" -ENUMS.Storage.weapons.containers.alq_184long="weapons.containers.alq-184long" -ENUMS.Storage.weapons.nurs.SNEB_TYPE259E_H1="weapons.nurs.SNEB_TYPE259E_H1" -ENUMS.Storage.weapons.bombs.British_SAP_250LB_Bomb_Mk5="weapons.bombs.British_SAP_250LB_Bomb_Mk5" -ENUMS.Storage.weapons.bombs.Transport_UAZ_469_Air_3747lb="weapons.bombs.Transport UAZ-469 Air [3747lb]" -ENUMS.Storage.weapons.bombs.Mk_83CT="weapons.bombs.Mk_83CT" -ENUMS.Storage.weapons.missiles.AIM_7P="weapons.missiles.AIM-7P" -ENUMS.Storage.weapons.missiles.AT_6="weapons.missiles.AT_6" -ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_GREEN="weapons.nurs.SNEB_TYPE254_H1_GREEN" -ENUMS.Storage.weapons.nurs.SNEB_TYPE250_F1B="weapons.nurs.SNEB_TYPE250_F1B" -ENUMS.Storage.weapons.containers.U22A="weapons.containers.U22A" -ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk1="weapons.bombs.British_GP_250LB_Bomb_Mk1" -ENUMS.Storage.weapons.bombs.CBU_105="weapons.bombs.CBU_105" -ENUMS.Storage.weapons.droptanks.FW_190_Fuel_Tank="weapons.droptanks.FW-190_Fuel-Tank" -ENUMS.Storage.weapons.missiles.X_58="weapons.missiles.X_58" -ENUMS.Storage.weapons.missiles.BK90_MJ1_MJ2="weapons.missiles.BK90_MJ1_MJ2" -ENUMS.Storage.weapons.missiles.TGM_65D="weapons.missiles.TGM_65D" -ENUMS.Storage.weapons.containers.BRD_4_250="weapons.containers.BRD-4-250" -ENUMS.Storage.weapons.missiles.P_73="weapons.missiles.P_73" -ENUMS.Storage.weapons.bombs.AN_M66="weapons.bombs.AN_M66" -ENUMS.Storage.weapons.bombs.APC_LAV_25_Air_22520lb="weapons.bombs.APC LAV-25 Air [22520lb]" -ENUMS.Storage.weapons.missiles.AIM_7MH="weapons.missiles.AIM-7MH" -ENUMS.Storage.weapons.containers.MB339_TravelPod="weapons.containers.MB339_TravelPod" -ENUMS.Storage.weapons.bombs.GBU_12="weapons.bombs.GBU_12" -ENUMS.Storage.weapons.bombs.SC_250_T3_J="weapons.bombs.SC_250_T3_J" -ENUMS.Storage.weapons.missiles.KD_20="weapons.missiles.KD-20" -ENUMS.Storage.weapons.missiles.AGM_86C="weapons.missiles.AGM_86C" -ENUMS.Storage.weapons.missiles.X_35="weapons.missiles.X_35" -ENUMS.Storage.weapons.bombs.MK106="weapons.bombs.MK106" -ENUMS.Storage.weapons.bombs.BETAB_500S="weapons.bombs.BETAB-500S" -ENUMS.Storage.weapons.nurs.C_5="weapons.nurs.C_5" -ENUMS.Storage.weapons.nurs.S_24B="weapons.nurs.S-24B" -ENUMS.Storage.weapons.bombs.British_MC_500LB_Bomb_Mk2="weapons.bombs.British_MC_500LB_Bomb_Mk2" -ENUMS.Storage.weapons.containers.ANAWW_13="weapons.containers.ANAWW_13" -ENUMS.Storage.weapons.droptanks.droptank_108_gal="weapons.droptanks.droptank_108_gal" -ENUMS.Storage.weapons.droptanks.DFT_300_GAL_A4E_LR="weapons.droptanks.DFT_300_GAL_A4E_LR" -ENUMS.Storage.weapons.bombs.CBU_87="weapons.bombs.CBU_87" -ENUMS.Storage.weapons.missiles.GAR_8="weapons.missiles.GAR-8" -ENUMS.Storage.weapons.bombs.BELOUGA="weapons.bombs.BELOUGA" -ENUMS.Storage.weapons.containers.EclairM_33="weapons.containers.{EclairM_33}" -ENUMS.Storage.weapons.bombs.ART_2S9_NONA_Air_19140lb="weapons.bombs.ART 2S9 NONA Air [19140lb]" -ENUMS.Storage.weapons.bombs.BR_250="weapons.bombs.BR_250" -ENUMS.Storage.weapons.bombs.IAB_500="weapons.bombs.IAB-500" -ENUMS.Storage.weapons.containers.AN_ASQ_228="weapons.containers.AN_ASQ_228" -ENUMS.Storage.weapons.missiles.P_27P="weapons.missiles.P_27P" -ENUMS.Storage.weapons.bombs.SD_250_Stg="weapons.bombs.SD_250_Stg" -ENUMS.Storage.weapons.missiles.R_530F_IR="weapons.missiles.R_530F_IR" -ENUMS.Storage.weapons.bombs.British_SAP_500LB_Bomb_Mk5="weapons.bombs.British_SAP_500LB_Bomb_Mk5" -ENUMS.Storage.weapons.bombs.FAB_250M54="weapons.bombs.FAB-250M54" -ENUMS.Storage.weapons.containers.M2KC_AAF="weapons.containers.{M2KC_AAF}" -ENUMS.Storage.weapons.missiles.CM_802AKG_AI="weapons.missiles.CM-802AKG_AI" -ENUMS.Storage.weapons.bombs.CBU_103="weapons.bombs.CBU_103" -ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_RED="weapons.containers.{US_M10_SMOKE_TANK_RED}" -ENUMS.Storage.weapons.missiles.X_29T="weapons.missiles.X_29T" -ENUMS.Storage.weapons.bombs.HEMTT_TFFT_34400lb="weapons.bombs.HEMTT TFFT [34400lb]" -ENUMS.Storage.weapons.missiles.C_701IR="weapons.missiles.C-701IR" -ENUMS.Storage.weapons.containers.fullCargoSeats="weapons.containers.fullCargoSeats" -ENUMS.Storage.weapons.bombs.GBU_15_V_31_B="weapons.bombs.GBU_15_V_31_B" -ENUMS.Storage.weapons.bombs.APC_M1043_HMMWV_Armament_Air_7023lb="weapons.bombs.APC M1043 HMMWV Armament Air [7023lb]" -ENUMS.Storage.weapons.missiles.PL_5EII="weapons.missiles.PL-5EII" -ENUMS.Storage.weapons.bombs.SC_250_T1_L2="weapons.bombs.SC_250_T1_L2" -ENUMS.Storage.weapons.torpedoes.mk46torp_name="weapons.torpedoes.mk46torp_name" -ENUMS.Storage.weapons.containers.F_15E_AAQ_33_XR_ATP_SE="weapons.containers.F-15E_AAQ-33_XR_ATP-SE" -ENUMS.Storage.weapons.missiles.AIM_7="weapons.missiles.AIM_7" -ENUMS.Storage.weapons.missiles.AGM_122="weapons.missiles.AGM_122" -ENUMS.Storage.weapons.bombs.HEBOMB="weapons.bombs.HEBOMB" -ENUMS.Storage.weapons.bombs.CBU_97="weapons.bombs.CBU_97" -ENUMS.Storage.weapons.bombs.MK_81SE="weapons.bombs.MK-81SE" -ENUMS.Storage.weapons.nurs.Zuni_127="weapons.nurs.Zuni_127" -ENUMS.Storage.weapons.containers.M2KC_AGF="weapons.containers.{M2KC_AGF}" -ENUMS.Storage.weapons.droptanks.Hercules_ExtFuelTank="weapons.droptanks.Hercules_ExtFuelTank" -ENUMS.Storage.weapons.containers.SMOKE_WHITE="weapons.containers.{SMOKE_WHITE}" -ENUMS.Storage.weapons.droptanks.droptank_150_gal="weapons.droptanks.droptank_150_gal" -ENUMS.Storage.weapons.nurs.HYDRA_70_WTU1B="weapons.nurs.HYDRA_70_WTU1B" -ENUMS.Storage.weapons.missiles.GB_6_SFW="weapons.missiles.GB-6-SFW" -ENUMS.Storage.weapons.missiles.KD_63="weapons.missiles.KD-63" -ENUMS.Storage.weapons.bombs.GBU_28="weapons.bombs.GBU_28" -ENUMS.Storage.weapons.nurs.C_8CM_YE="weapons.nurs.C_8CM_YE" -ENUMS.Storage.weapons.droptanks.HB_F14_EXT_DROPTANK="weapons.droptanks.HB_F14_EXT_DROPTANK" -ENUMS.Storage.weapons.missiles.Super_530F="weapons.missiles.Super_530F" -ENUMS.Storage.weapons.missiles.Ataka_9M220="weapons.missiles.Ataka_9M220" -ENUMS.Storage.weapons.bombs.BDU_33="weapons.bombs.BDU_33" -ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk4="weapons.bombs.British_GP_250LB_Bomb_Mk4" -ENUMS.Storage.weapons.missiles.TOW="weapons.missiles.TOW" -ENUMS.Storage.weapons.bombs.ATGM_M1045_HMMWV_TOW_Air_7183lb="weapons.bombs.ATGM M1045 HMMWV TOW Air [7183lb]" -ENUMS.Storage.weapons.missiles.X_25MR="weapons.missiles.X_25MR" -ENUMS.Storage.weapons.droptanks.fueltank230="weapons.droptanks.fueltank230" -ENUMS.Storage.weapons.droptanks.PTB_490C_MIG21="weapons.droptanks.PTB-490C-MIG21" -ENUMS.Storage.weapons.bombs.M1025_HMMWV_Air_6160lb="weapons.bombs.M1025 HMMWV Air [6160lb]" -ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_GREEN="weapons.nurs.SNEB_TYPE254_F1B_GREEN" -ENUMS.Storage.weapons.missiles.R_550="weapons.missiles.R_550" -ENUMS.Storage.weapons.bombs.KAB_1500LG="weapons.bombs.KAB_1500LG" -ENUMS.Storage.weapons.missiles.AGM_84D="weapons.missiles.AGM_84D" -ENUMS.Storage.weapons.missiles.YJ_83K="weapons.missiles.YJ-83K" -ENUMS.Storage.weapons.missiles.AIM_54C_Mk47="weapons.missiles.AIM_54C_Mk47" -ENUMS.Storage.weapons.missiles.BRM_1_90MM="weapons.missiles.BRM-1_90MM" -ENUMS.Storage.weapons.missiles.Ataka_9M120F="weapons.missiles.Ataka_9M120F" -ENUMS.Storage.weapons.droptanks.Eleven00L_Tank="weapons.droptanks.1100L Tank" -ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP_100" -ENUMS.Storage.weapons.adapters.lau_88="weapons.adapters.lau-88" -ENUMS.Storage.weapons.missiles.P_40T="weapons.missiles.P_40T" -ENUMS.Storage.weapons.missiles.GB_6="weapons.missiles.GB-6" -ENUMS.Storage.weapons.bombs.FAB_250M54TU="weapons.bombs.FAB-250M54TU" -ENUMS.Storage.weapons.missiles.DWS39_MJ1="weapons.missiles.DWS39_MJ1" -ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM-802AKG" -ENUMS.Storage.weapons.bombs.FAB_250="weapons.bombs.FAB_250" -ENUMS.Storage.weapons.missiles.C_802AK="weapons.missiles.C_802AK" -ENUMS.Storage.weapons.bombs.SD_500_A="weapons.bombs.SD_500_A" -ENUMS.Storage.weapons.bombs.GBU_32_V_2B="weapons.bombs.GBU_32_V_2B" -ENUMS.Storage.weapons.containers.marder="weapons.containers.marder" -ENUMS.Storage.weapons.missiles.ADM_141B="weapons.missiles.ADM_141B" -ENUMS.Storage.weapons.bombs.ROCKEYE="weapons.bombs.ROCKEYE" -ENUMS.Storage.weapons.missiles.BK90_MJ1="weapons.missiles.BK90_MJ1" -ENUMS.Storage.weapons.containers.BTR_80="weapons.containers.BTR-80" -ENUMS.Storage.weapons.bombs.SAM_ROLAND_ADS_34720lb="weapons.bombs.SAM ROLAND ADS [34720lb]" -ENUMS.Storage.weapons.containers.wmd7="weapons.containers.wmd7" -ENUMS.Storage.weapons.missiles.C_701T="weapons.missiles.C-701T" -ENUMS.Storage.weapons.missiles.AIM_7E_2="weapons.missiles.AIM-7E-2" -ENUMS.Storage.weapons.nurs.HVAR="weapons.nurs.HVAR" -ENUMS.Storage.weapons.containers.HMMWV_M1043="weapons.containers.HMMWV_M1043" -ENUMS.Storage.weapons.droptanks.PTB_800_MIG21="weapons.droptanks.PTB-800-MIG21" -ENUMS.Storage.weapons.missiles.AGM_114="weapons.missiles.AGM_114" -ENUMS.Storage.weapons.bombs.APC_M1126_Stryker_ICV_29542lb="weapons.bombs.APC M1126 Stryker ICV [29542lb]" -ENUMS.Storage.weapons.bombs.APC_M113_Air_21624lb="weapons.bombs.APC M113 Air [21624lb]" -ENUMS.Storage.weapons.bombs.M_117="weapons.bombs.M_117" -ENUMS.Storage.weapons.missiles.AGM_65D="weapons.missiles.AGM_65D" -ENUMS.Storage.weapons.droptanks.MB339_TT320_L="weapons.droptanks.MB339_TT320_L" -ENUMS.Storage.weapons.missiles.AGM_86="weapons.missiles.AGM_86" -ENUMS.Storage.weapons.bombs.BDU_45LGB="weapons.bombs.BDU_45LGB" -ENUMS.Storage.weapons.missiles.AGM_65H="weapons.missiles.AGM_65H" -ENUMS.Storage.weapons.nurs.RS_82="weapons.nurs.RS-82" -ENUMS.Storage.weapons.nurs.SNEB_TYPE252_F1B="weapons.nurs.SNEB_TYPE252_F1B" -ENUMS.Storage.weapons.bombs.BL_755="weapons.bombs.BL_755" -ENUMS.Storage.weapons.containers.F_15E_AAQ_28_LITENING="weapons.containers.F-15E_AAQ-28_LITENING" -ENUMS.Storage.weapons.nurs.SNEB_TYPE256_F1B="weapons.nurs.SNEB_TYPE256_F1B" -ENUMS.Storage.weapons.missiles.AGM_84H="weapons.missiles.AGM_84H" -ENUMS.Storage.weapons.missiles.AIM_54="weapons.missiles.AIM_54" -ENUMS.Storage.weapons.missiles.X_31A="weapons.missiles.X_31A" -ENUMS.Storage.weapons.bombs.KAB_500Kr="weapons.bombs.KAB_500Kr" -ENUMS.Storage.weapons.containers.SPS_141_100="weapons.containers.SPS-141-100" -ENUMS.Storage.weapons.missiles.BK90_MJ2="weapons.missiles.BK90_MJ2" -ENUMS.Storage.weapons.missiles.Super_530D="weapons.missiles.Super_530D" -ENUMS.Storage.weapons.bombs.CBU_52B="weapons.bombs.CBU_52B" -ENUMS.Storage.weapons.droptanks.PTB_450="weapons.droptanks.PTB-450" -ENUMS.Storage.weapons.bombs.IFV_MCV_80_34720lb="weapons.bombs.IFV MCV-80 [34720lb]" -ENUMS.Storage.weapons.containers.Two_c9="weapons.containers.2-c9" -ENUMS.Storage.weapons.missiles.AIM_9JULI="weapons.missiles.AIM-9JULI" -ENUMS.Storage.weapons.droptanks.MB339_TT500_R="weapons.droptanks.MB339_TT500_R" -ENUMS.Storage.weapons.nurs.C_8CM="weapons.nurs.C_8CM" -ENUMS.Storage.weapons.containers.BARAX="weapons.containers.BARAX" -ENUMS.Storage.weapons.missiles.P_40R="weapons.missiles.P_40R" -ENUMS.Storage.weapons.missiles.YJ_12="weapons.missiles.YJ-12" -ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM_802AKG" -ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_YELLOW="weapons.nurs.SNEB_TYPE254_H1_YELLOW" -ENUMS.Storage.weapons.bombs.Durandal="weapons.bombs.Durandal" -ENUMS.Storage.weapons.droptanks.i16_eft="weapons.droptanks.i16_eft" -ENUMS.Storage.weapons.droptanks.AV8BNA_AERO1D_EMPTY="weapons.droptanks.AV8BNA_AERO1D_EMPTY" -ENUMS.Storage.weapons.containers.Hercules_Battle_Station_TGP="weapons.containers.Hercules_Battle_Station_TGP" -ENUMS.Storage.weapons.nurs.C_8CM_VT="weapons.nurs.C_8CM_VT" -ENUMS.Storage.weapons.missiles.PL_12="weapons.missiles.PL-12" -ENUMS.Storage.weapons.missiles.R_3R="weapons.missiles.R-3R" -ENUMS.Storage.weapons.bombs.GBU_54_V_1B="weapons.bombs.GBU_54_V_1B" -ENUMS.Storage.weapons.droptanks.MB339_TT320_R="weapons.droptanks.MB339_TT320_R" -ENUMS.Storage.weapons.bombs.RN_24="weapons.bombs.RN-24" -ENUMS.Storage.weapons.containers.Twoc6m="weapons.containers.2c6m" -ENUMS.Storage.weapons.bombs.ARV_BRDM_2_Air_12320lb="weapons.bombs.ARV BRDM-2 Air [12320lb]" -ENUMS.Storage.weapons.bombs.ARV_BRDM_2_Skid_12210lb="weapons.bombs.ARV BRDM-2 Skid [12210lb]" -ENUMS.Storage.weapons.nurs.SNEB_TYPE251_F1B="weapons.nurs.SNEB_TYPE251_F1B" -ENUMS.Storage.weapons.missiles.X_41="weapons.missiles.X_41" -ENUMS.Storage.weapons.containers.MIG21_SMOKE_WHITE="weapons.containers.{MIG21_SMOKE_WHITE}" -ENUMS.Storage.weapons.bombs.MK_82AIR="weapons.bombs.MK_82AIR" -ENUMS.Storage.weapons.missiles.R_530F_EM="weapons.missiles.R_530F_EM" -ENUMS.Storage.weapons.bombs.SAMP400LD="weapons.bombs.SAMP400LD" -ENUMS.Storage.weapons.bombs.FAB_50="weapons.bombs.FAB_50" -ENUMS.Storage.weapons.bombs.AB_250_2_SD_10A="weapons.bombs.AB_250_2_SD_10A" -ENUMS.Storage.weapons.missiles.ADM_141A="weapons.missiles.ADM_141A" -ENUMS.Storage.weapons.containers.KBpod="weapons.containers.KBpod" -ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk4="weapons.bombs.British_GP_500LB_Bomb_Mk4" -ENUMS.Storage.weapons.missiles.AGM_65E="weapons.missiles.AGM_65E" -ENUMS.Storage.weapons.containers.sa342_dipole_antenna="weapons.containers.sa342_dipole_antenna" -ENUMS.Storage.weapons.bombs.OFAB_100_Jupiter="weapons.bombs.OFAB-100 Jupiter" -ENUMS.Storage.weapons.nurs.SNEB_TYPE257_F1B="weapons.nurs.SNEB_TYPE257_F1B" -ENUMS.Storage.weapons.missiles.Rb_04E_for_A_I="weapons.missiles.Rb 04E (for A.I.)" -ENUMS.Storage.weapons.bombs.AN_M66A2="weapons.bombs.AN-M66A2" -ENUMS.Storage.weapons.missiles.P_27T="weapons.missiles.P_27T" -ENUMS.Storage.weapons.droptanks.LNS_VIG_XTANK="weapons.droptanks.LNS_VIG_XTANK" -ENUMS.Storage.weapons.missiles.R_55="weapons.missiles.R-55" -ENUMS.Storage.weapons.torpedoes.YU_6="weapons.torpedoes.YU-6" -ENUMS.Storage.weapons.bombs.British_MC_250LB_Bomb_Mk2="weapons.bombs.British_MC_250LB_Bomb_Mk2" -ENUMS.Storage.weapons.droptanks.PTB_120_F86F35="weapons.droptanks.PTB_120_F86F35" -ENUMS.Storage.weapons.missiles.PL_8B="weapons.missiles.PL-8B" -ENUMS.Storage.weapons.droptanks.F_15E_Drop_Tank_Empty="weapons.droptanks.F-15E_Drop_Tank_Empty" -ENUMS.Storage.weapons.nurs.British_HE_60LBFNo1_3INCHNo1="weapons.nurs.British_HE_60LBFNo1_3INCHNo1" -ENUMS.Storage.weapons.missiles.P_77="weapons.missiles.P_77" -ENUMS.Storage.weapons.torpedoes.LTF_5B="weapons.torpedoes.LTF_5B" -ENUMS.Storage.weapons.missiles.R_3S="weapons.missiles.R-3S" -ENUMS.Storage.weapons.nurs.SNEB_TYPE253_H1="weapons.nurs.SNEB_TYPE253_H1" -ENUMS.Storage.weapons.missiles.PL_8A="weapons.missiles.PL-8A" -ENUMS.Storage.weapons.bombs.APC_BTR_82A_Skid_24888lb="weapons.bombs.APC BTR-82A Skid [24888lb]" -ENUMS.Storage.weapons.containers.Sborka="weapons.containers.Sborka" -ENUMS.Storage.weapons.missiles.AGM_65L="weapons.missiles.AGM_65L" -ENUMS.Storage.weapons.missiles.X_28="weapons.missiles.X_28" -ENUMS.Storage.weapons.missiles.TGM_65G="weapons.missiles.TGM_65G" -ENUMS.Storage.weapons.nurs.SNEB_TYPE257_H1="weapons.nurs.SNEB_TYPE257_H1" -ENUMS.Storage.weapons.missiles.RB75B="weapons.missiles.RB75B" -ENUMS.Storage.weapons.missiles.X_25ML="weapons.missiles.X_25ML" -ENUMS.Storage.weapons.droptanks.FPU_8A="weapons.droptanks.FPU_8A" -ENUMS.Storage.weapons.bombs.BLG66="weapons.bombs.BLG66" -ENUMS.Storage.weapons.nurs.C_8CM_RD="weapons.nurs.C_8CM_RD" -ENUMS.Storage.weapons.containers.EclairM_06="weapons.containers.{EclairM_06}" -ENUMS.Storage.weapons.bombs.RBK_500AO="weapons.bombs.RBK_500AO" -ENUMS.Storage.weapons.missiles.AIM_9P="weapons.missiles.AIM-9P" -ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk4_Short="weapons.bombs.British_GP_500LB_Bomb_Mk4_Short" -ENUMS.Storage.weapons.containers.MB339_Vinten="weapons.containers.MB339_Vinten" -ENUMS.Storage.weapons.missiles.Rb_15F="weapons.missiles.Rb 15F" -ENUMS.Storage.weapons.nurs.ARAKM70BHE="weapons.nurs.ARAKM70BHE" -ENUMS.Storage.weapons.bombs.AAA_Vulcan_M163_Air_21666lb="weapons.bombs.AAA Vulcan M163 Air [21666lb]" -ENUMS.Storage.weapons.missiles.X_29L="weapons.missiles.X_29L" -ENUMS.Storage.weapons.containers.F14_LANTIRN_TP="weapons.containers.{F14-LANTIRN-TP}" -ENUMS.Storage.weapons.bombs.FAB_250_M62="weapons.bombs.FAB-250-M62" -ENUMS.Storage.weapons.missiles.AIM_120C="weapons.missiles.AIM_120C" -ENUMS.Storage.weapons.bombs.EWR_SBORKA_Air_21624lb="weapons.bombs.EWR SBORKA Air [21624lb]" -ENUMS.Storage.weapons.bombs.SAMP250LD="weapons.bombs.SAMP250LD" -ENUMS.Storage.weapons.droptanks.Spitfire_slipper_tank="weapons.droptanks.Spitfire_slipper_tank" -ENUMS.Storage.weapons.missiles.LS_6_500="weapons.missiles.LS-6-500" -ENUMS.Storage.weapons.bombs.GBU_31_V_4B="weapons.bombs.GBU_31_V_4B" -ENUMS.Storage.weapons.droptanks.PTB400_MIG15="weapons.droptanks.PTB400_MIG15" -ENUMS.Storage.weapons.containers.m_113="weapons.containers.m-113" -ENUMS.Storage.weapons.bombs.SPG_M1128_Stryker_MGS_33036lb="weapons.bombs.SPG M1128 Stryker MGS [33036lb]" -ENUMS.Storage.weapons.missiles.AIM_9L="weapons.missiles.AIM-9L" -ENUMS.Storage.weapons.missiles.AIM_9X="weapons.missiles.AIM_9X" -ENUMS.Storage.weapons.nurs.C_8="weapons.nurs.C_8" -ENUMS.Storage.weapons.bombs.SAM_CHAPARRAL_Skid_21516lb="weapons.bombs.SAM CHAPARRAL Skid [21516lb]" -ENUMS.Storage.weapons.missiles.P_27TE="weapons.missiles.P_27TE" -ENUMS.Storage.weapons.bombs.ODAB_500PM="weapons.bombs.ODAB-500PM" -ENUMS.Storage.weapons.bombs.MK77mod1_WPN="weapons.bombs.MK77mod1-WPN" -ENUMS.Storage.weapons.droptanks.PTB400_MIG19="weapons.droptanks.PTB400_MIG19" -ENUMS.Storage.weapons.torpedoes.Mark_46="weapons.torpedoes.Mark_46" -ENUMS.Storage.weapons.containers.rightSeat="weapons.containers.rightSeat" -ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_ORANGE="weapons.containers.{US_M10_SMOKE_TANK_ORANGE}" -ENUMS.Storage.weapons.bombs.SAB_100MN="weapons.bombs.SAB_100MN" -ENUMS.Storage.weapons.nurs.FFAR_Mk5_HEAT="weapons.nurs.FFAR Mk5 HEAT" -ENUMS.Storage.weapons.bombs.IFV_TPZ_FUCH_33440lb="weapons.bombs.IFV TPZ FUCH [33440lb]" -ENUMS.Storage.weapons.bombs.IFV_M2A2_Bradley_34720lb="weapons.bombs.IFV M2A2 Bradley [34720lb]" -ENUMS.Storage.weapons.bombs.MK77mod0_WPN="weapons.bombs.MK77mod0-WPN" -ENUMS.Storage.weapons.containers.ASO_2="weapons.containers.ASO-2" -ENUMS.Storage.weapons.bombs.Mk_84AIR_GP="weapons.bombs.Mk_84AIR_GP" -ENUMS.Storage.weapons.nurs.S_24A="weapons.nurs.S-24A" -ENUMS.Storage.weapons.bombs.RBK_250_275_AO_1SCH="weapons.bombs.RBK_250_275_AO_1SCH" -ENUMS.Storage.weapons.bombs.Transport_Tigr_Skid_15730lb="weapons.bombs.Transport Tigr Skid [15730lb]" -ENUMS.Storage.weapons.missiles.AIM_7F="weapons.missiles.AIM-7F" -ENUMS.Storage.weapons.bombs.CBU_99="weapons.bombs.CBU_99" -ENUMS.Storage.weapons.bombs.LUU_2B="weapons.bombs.LUU_2B" -ENUMS.Storage.weapons.bombs.FAB_500TA="weapons.bombs.FAB-500TA" -ENUMS.Storage.weapons.missiles.AGR_20_M282="weapons.missiles.AGR_20_M282" -ENUMS.Storage.weapons.droptanks.MB339_FT330="weapons.droptanks.MB339_FT330" -ENUMS.Storage.weapons.bombs.SAMP125LD="weapons.bombs.SAMP125LD" -ENUMS.Storage.weapons.missiles.X_25MP="weapons.missiles.X_25MP" -ENUMS.Storage.weapons.nurs.SNEB_TYPE252_H1="weapons.nurs.SNEB_TYPE252_H1" -ENUMS.Storage.weapons.missiles.AGM_65F="weapons.missiles.AGM_65F" -ENUMS.Storage.weapons.missiles.AIM_9P5="weapons.missiles.AIM-9P5" -ENUMS.Storage.weapons.bombs.Transport_Tigr_Air_15900lb="weapons.bombs.Transport Tigr Air [15900lb]" -ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_RED="weapons.nurs.SNEB_TYPE254_H1_RED" -ENUMS.Storage.weapons.nurs.FFAR_Mk1_HE="weapons.nurs.FFAR Mk1 HE" -ENUMS.Storage.weapons.nurs.SPRD_99="weapons.nurs.SPRD-99" -ENUMS.Storage.weapons.bombs.BIN_200="weapons.bombs.BIN_200" -ENUMS.Storage.weapons.bombs.BLU_4B_GROUP="weapons.bombs.BLU_4B_GROUP" -ENUMS.Storage.weapons.bombs.GBU_24="weapons.bombs.GBU_24" -ENUMS.Storage.weapons.missiles.Rb_04E="weapons.missiles.Rb 04E" -ENUMS.Storage.weapons.missiles.Rb_74="weapons.missiles.Rb 74" -ENUMS.Storage.weapons.containers.leftSeat="weapons.containers.leftSeat" -ENUMS.Storage.weapons.bombs.LS_6_100="weapons.bombs.LS-6-100" -ENUMS.Storage.weapons.bombs.Transport_URAL_375_14815lb="weapons.bombs.Transport URAL-375 [14815lb]" -ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_GREEN="weapons.containers.{US_M10_SMOKE_TANK_GREEN}" -ENUMS.Storage.weapons.missiles.X_22="weapons.missiles.X_22" -ENUMS.Storage.weapons.containers.FAS="weapons.containers.FAS" -ENUMS.Storage.weapons.nurs.S_25_O="weapons.nurs.S-25-O" -ENUMS.Storage.weapons.droptanks.para="weapons.droptanks.para" -ENUMS.Storage.weapons.droptanks.F_15E_Drop_Tank="weapons.droptanks.F-15E_Drop_Tank" -ENUMS.Storage.weapons.droptanks.M2KC_08_RPL541_EMPTY="weapons.droptanks.M2KC_08_RPL541_EMPTY" -ENUMS.Storage.weapons.missiles.X_31P="weapons.missiles.X_31P" -ENUMS.Storage.weapons.bombs.RBK_500U="weapons.bombs.RBK_500U" -ENUMS.Storage.weapons.missiles.AIM_54A_Mk47="weapons.missiles.AIM_54A_Mk47" -ENUMS.Storage.weapons.droptanks.oiltank="weapons.droptanks.oiltank" -ENUMS.Storage.weapons.missiles.AGM_154B="weapons.missiles.AGM_154B" -ENUMS.Storage.weapons.containers.MB339_SMOKE_POD="weapons.containers.MB339_SMOKE-POD" -ENUMS.Storage.weapons.containers.ECM_POD_L_175V="weapons.containers.{ECM_POD_L_175V}" -ENUMS.Storage.weapons.droptanks.PTB_580G_F1="weapons.droptanks.PTB_580G_F1" -ENUMS.Storage.weapons.containers.EclairM_15="weapons.containers.{EclairM_15}" -ENUMS.Storage.weapons.containers.F_15E_AAQ_13_LANTIRN="weapons.containers.F-15E_AAQ-13_LANTIRN" -ENUMS.Storage.weapons.droptanks.Eight00L_Tank_Empty="weapons.droptanks.800L Tank Empty" -ENUMS.Storage.weapons.containers.One6c_hts_pod="weapons.containers.16c_hts_pod" -ENUMS.Storage.weapons.bombs.AN_M81="weapons.bombs.AN-M81" -ENUMS.Storage.weapons.droptanks.Mosquito_Drop_Tank_100gal="weapons.droptanks.Mosquito_Drop_Tank_100gal" -ENUMS.Storage.weapons.droptanks.Mosquito_Drop_Tank_50gal="weapons.droptanks.Mosquito_Drop_Tank_50gal" -ENUMS.Storage.weapons.droptanks.DFT_150_GAL_A4E="weapons.droptanks.DFT_150_GAL_A4E" -ENUMS.Storage.weapons.missiles.AIM_9="weapons.missiles.AIM_9" -ENUMS.Storage.weapons.bombs.IFV_BTR_D_Air_18040lb="weapons.bombs.IFV BTR-D Air [18040lb]" -ENUMS.Storage.weapons.containers.EclairM_42="weapons.containers.{EclairM_42}" -ENUMS.Storage.weapons.bombs.KAB_1500T="weapons.bombs.KAB_1500T" -ENUMS.Storage.weapons.droptanks.PTB_490_MIG21="weapons.droptanks.PTB-490-MIG21" -ENUMS.Storage.weapons.droptanks.PTB_200_F86F35="weapons.droptanks.PTB_200_F86F35" -ENUMS.Storage.weapons.droptanks.PTB760_MIG19="weapons.droptanks.PTB760_MIG19" -ENUMS.Storage.weapons.bombs.GBU_43_B_MOAB="weapons.bombs.GBU-43/B(MOAB)" -ENUMS.Storage.weapons.torpedoes.G7A_T1="weapons.torpedoes.G7A_T1" -ENUMS.Storage.weapons.bombs.IFV_BMD_1_Air_18040lb="weapons.bombs.IFV BMD-1 Air [18040lb]" -ENUMS.Storage.weapons.bombs.SAM_LINEBACKER_34720lb="weapons.bombs.SAM LINEBACKER [34720lb]" -ENUMS.Storage.weapons.containers.ais_pod_t50_r="weapons.containers.ais-pod-t50_r" -ENUMS.Storage.weapons.containers.CE2_SMOKE_WHITE="weapons.containers.{CE2_SMOKE_WHITE}" -ENUMS.Storage.weapons.droptanks.fuel_tank_230="weapons.droptanks.fuel_tank_230" -ENUMS.Storage.weapons.droptanks.M2KC_RPL_522="weapons.droptanks.M2KC_RPL_522" -ENUMS.Storage.weapons.missiles.AGM_130="weapons.missiles.AGM_130" -ENUMS.Storage.weapons.droptanks.Eight00L_Tank="weapons.droptanks.800L Tank" -ENUMS.Storage.weapons.bombs.IFV_BTR_D_Skid_17930lb="weapons.bombs.IFV BTR-D Skid [17930lb]" -ENUMS.Storage.weapons.containers.bmp_1="weapons.containers.bmp-1" -ENUMS.Storage.weapons.bombs.GBU_31="weapons.bombs.GBU_31" -ENUMS.Storage.weapons.containers.aaq_28LEFT_litening="weapons.containers.aaq-28LEFT litening" -ENUMS.Storage.weapons.missiles.Kh_66_Grom="weapons.missiles.Kh-66_Grom" -ENUMS.Storage.weapons.containers.MIG21_SMOKE_RED="weapons.containers.{MIG21_SMOKE_RED}" -ENUMS.Storage.weapons.containers.U22="weapons.containers.U22" -ENUMS.Storage.weapons.bombs.IFV_BMD_1_Skid_17930lb="weapons.bombs.IFV BMD-1 Skid [17930lb]" -ENUMS.Storage.weapons.droptanks.Bidon="weapons.droptanks.Bidon" -ENUMS.Storage.weapons.bombs.GBU_31_V_2B="weapons.bombs.GBU_31_V_2B" -ENUMS.Storage.weapons.bombs.Mk_82Y="weapons.bombs.Mk_82Y" -ENUMS.Storage.weapons.containers.pl5eii="weapons.containers.pl5eii" -ENUMS.Storage.weapons.bombs.RBK_500U_OAB_2_5RT="weapons.bombs.RBK_500U_OAB_2_5RT" -ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk5="weapons.bombs.British_GP_500LB_Bomb_Mk5" -ENUMS.Storage.weapons.containers.Eclair="weapons.containers.{Eclair}" -ENUMS.Storage.weapons.nurs.S5MO_HEFRAG_FFAR="weapons.nurs.S5MO_HEFRAG_FFAR" -ENUMS.Storage.weapons.bombs.BETAB_500M="weapons.bombs.BETAB-500M" -ENUMS.Storage.weapons.bombs.Transport_M818_16000lb="weapons.bombs.Transport M818 [16000lb]" -ENUMS.Storage.weapons.bombs.British_MC_250LB_Bomb_Mk1="weapons.bombs.British_MC_250LB_Bomb_Mk1" -ENUMS.Storage.weapons.nurs.SNEB_TYPE251_H1="weapons.nurs.SNEB_TYPE251_H1" -ENUMS.Storage.weapons.bombs.TYPE_200A="weapons.bombs.TYPE-200A" -ENUMS.Storage.weapons.nurs.HYDRA_70_M151="weapons.nurs.HYDRA_70_M151" -ENUMS.Storage.weapons.bombs.IFV_BMP_3_32912lb="weapons.bombs.IFV BMP-3 [32912lb]" -ENUMS.Storage.weapons.bombs.APC_MTLB_Air_26400lb="weapons.bombs.APC MTLB Air [26400lb]" -ENUMS.Storage.weapons.nurs.HYDRA_70_M229="weapons.nurs.HYDRA_70_M229" -ENUMS.Storage.weapons.bombs.BDU_45="weapons.bombs.BDU_45" -ENUMS.Storage.weapons.bombs.OFAB_100_120TU="weapons.bombs.OFAB-100-120TU" -ENUMS.Storage.weapons.missiles.AIM_9J="weapons.missiles.AIM-9J" -ENUMS.Storage.weapons.nurs.ARF8M3API="weapons.nurs.ARF8M3API" -ENUMS.Storage.weapons.bombs.BetAB_500ShP="weapons.bombs.BetAB_500ShP" -ENUMS.Storage.weapons.nurs.C_8OFP2="weapons.nurs.C_8OFP2" -ENUMS.Storage.weapons.bombs.GBU_10="weapons.bombs.GBU_10" -ENUMS.Storage.weapons.bombs.APC_MTLB_Skid_26290lb="weapons.bombs.APC MTLB Skid [26290lb]" -ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_RED="weapons.nurs.SNEB_TYPE254_F1B_RED" -ENUMS.Storage.weapons.missiles.X_65="weapons.missiles.X_65" -ENUMS.Storage.weapons.missiles.R_550_M1="weapons.missiles.R_550_M1" -ENUMS.Storage.weapons.missiles.AGM_65K="weapons.missiles.AGM_65K" -ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_YELLOW="weapons.nurs.SNEB_TYPE254_F1B_YELLOW" -ENUMS.Storage.weapons.missiles.AGM_88="weapons.missiles.AGM_88" -ENUMS.Storage.weapons.nurs.C_8OM="weapons.nurs.C_8OM" -ENUMS.Storage.weapons.bombs.SAM_ROLAND_LN_34720b="weapons.bombs.SAM ROLAND LN [34720b]" -ENUMS.Storage.weapons.missiles.AIM_120="weapons.missiles.AIM_120" -ENUMS.Storage.weapons.missiles.HOT3_MBDA="weapons.missiles.HOT3_MBDA" -ENUMS.Storage.weapons.missiles.R_13M="weapons.missiles.R-13M" -ENUMS.Storage.weapons.missiles.AIM_54C_Mk60="weapons.missiles.AIM_54C_Mk60" -ENUMS.Storage.weapons.bombs.AAA_GEPARD_34720lb="weapons.bombs.AAA GEPARD [34720lb]" -ENUMS.Storage.weapons.missiles.R_13M1="weapons.missiles.R-13M1" -ENUMS.Storage.weapons.bombs.APC_Cobra_Air_10912lb="weapons.bombs.APC Cobra Air [10912lb]" -ENUMS.Storage.weapons.bombs.RBK_250="weapons.bombs.RBK_250" -ENUMS.Storage.weapons.bombs.SC_500_J="weapons.bombs.SC_500_J" -ENUMS.Storage.weapons.missiles.AGM_114K="weapons.missiles.AGM_114K" -ENUMS.Storage.weapons.missiles.ALARM="weapons.missiles.ALARM" -ENUMS.Storage.weapons.bombs.Mk_83="weapons.bombs.Mk_83" -ENUMS.Storage.weapons.missiles.AGM_65B="weapons.missiles.AGM_65B" -ENUMS.Storage.weapons.bombs.MK_82SNAKEYE="weapons.bombs.MK_82SNAKEYE" -ENUMS.Storage.weapons.nurs.HYDRA_70_MK1="weapons.nurs.HYDRA_70_MK1" -ENUMS.Storage.weapons.bombs.BLG66_BELOUGA="weapons.bombs.BLG66_BELOUGA" -ENUMS.Storage.weapons.containers.EclairM_51="weapons.containers.{EclairM_51}" -ENUMS.Storage.weapons.missiles.AIM_54A_Mk60="weapons.missiles.AIM_54A_Mk60" -ENUMS.Storage.weapons.droptanks.DFT_300_GAL_A4E="weapons.droptanks.DFT_300_GAL_A4E" -ENUMS.Storage.weapons.bombs.ATGM_M1134_Stryker_30337lb="weapons.bombs.ATGM M1134 Stryker [30337lb]" -ENUMS.Storage.weapons.bombs.BAT_120="weapons.bombs.BAT-120" -ENUMS.Storage.weapons.missiles.DWS39_MJ1_MJ2="weapons.missiles.DWS39_MJ1_MJ2" -ENUMS.Storage.weapons.containers.SPRD="weapons.containers.SPRD" -ENUMS.Storage.weapons.bombs.BR_500="weapons.bombs.BR_500" -ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk1="weapons.bombs.British_GP_500LB_Bomb_Mk1" -ENUMS.Storage.weapons.bombs.BDU_50HD="weapons.bombs.BDU_50HD" -ENUMS.Storage.weapons.missiles.RS2US="weapons.missiles.RS2US" -ENUMS.Storage.weapons.bombs.IFV_BMP_2_25168lb="weapons.bombs.IFV BMP-2 [25168lb]" -ENUMS.Storage.weapons.bombs.SAMP400HD="weapons.bombs.SAMP400HD" -ENUMS.Storage.weapons.containers.Hercules_Battle_Station="weapons.containers.Hercules_Battle_Station" -ENUMS.Storage.weapons.bombs.AN_M64="weapons.bombs.AN_M64" -ENUMS.Storage.weapons.containers.rearCargoSeats="weapons.containers.rearCargoSeats" -ENUMS.Storage.weapons.bombs.Mk_82="weapons.bombs.Mk_82" -ENUMS.Storage.weapons.missiles.AKD_10="weapons.missiles.AKD-10" -ENUMS.Storage.weapons.bombs.BDU_50LGB="weapons.bombs.BDU_50LGB" -ENUMS.Storage.weapons.missiles.SD_10="weapons.missiles.SD-10" -ENUMS.Storage.weapons.containers.IRDeflector="weapons.containers.IRDeflector" -ENUMS.Storage.weapons.bombs.FAB_500="weapons.bombs.FAB_500" -ENUMS.Storage.weapons.bombs.KAB_500="weapons.bombs.KAB_500" -ENUMS.Storage.weapons.nurs.S_5M="weapons.nurs.S-5M" -ENUMS.Storage.weapons.missiles.MICA_R="weapons.missiles.MICA_R" -ENUMS.Storage.weapons.missiles.X_59M="weapons.missiles.X_59M" -ENUMS.Storage.weapons.nurs.UG_90MM="weapons.nurs.UG_90MM" -ENUMS.Storage.weapons.bombs.LYSBOMB="weapons.bombs.LYSBOMB" -ENUMS.Storage.weapons.nurs.R4M="weapons.nurs.R4M" -ENUMS.Storage.weapons.containers.dlpod_akg="weapons.containers.dlpod_akg" -ENUMS.Storage.weapons.missiles.LD_10="weapons.missiles.LD-10" -ENUMS.Storage.weapons.bombs.SC_50="weapons.bombs.SC_50" -ENUMS.Storage.weapons.nurs.HYDRA_70_MK5="weapons.nurs.HYDRA_70_MK5" -ENUMS.Storage.weapons.bombs.FAB_100M="weapons.bombs.FAB_100M" -ENUMS.Storage.weapons.missiles.Rb_24="weapons.missiles.Rb 24" -ENUMS.Storage.weapons.bombs.BDU_45B="weapons.bombs.BDU_45B" -ENUMS.Storage.weapons.missiles.GB_6_HE="weapons.missiles.GB-6-HE" -ENUMS.Storage.weapons.missiles.KD_63B="weapons.missiles.KD-63B" -ENUMS.Storage.weapons.missiles.P_27PE="weapons.missiles.P_27PE" -ENUMS.Storage.weapons.droptanks.PTB300_MIG15="weapons.droptanks.PTB300_MIG15" -ENUMS.Storage.weapons.bombs.Two50_3="weapons.bombs.250-3" -ENUMS.Storage.weapons.bombs.SC_500_L2="weapons.bombs.SC_500_L2" -ENUMS.Storage.weapons.containers.HMMWV_M1045="weapons.containers.HMMWV_M1045" -ENUMS.Storage.weapons.bombs.FAB_500M54TU="weapons.bombs.FAB-500M54TU" -ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_YELLOW="weapons.containers.{US_M10_SMOKE_TANK_YELLOW}" -ENUMS.Storage.weapons.containers.EclairM_60="weapons.containers.{EclairM_60}" -ENUMS.Storage.weapons.bombs.SAB_250_200="weapons.bombs.SAB_250_200" -ENUMS.Storage.weapons.bombs.FAB_100="weapons.bombs.FAB_100" -ENUMS.Storage.weapons.bombs.KAB_500S="weapons.bombs.KAB_500S" -ENUMS.Storage.weapons.missiles.AGM_45A="weapons.missiles.AGM_45A" -ENUMS.Storage.weapons.missiles.Kh25MP_PRGS1VP="weapons.missiles.Kh25MP_PRGS1VP" -ENUMS.Storage.weapons.nurs.S5M1_HEFRAG_FFAR="weapons.nurs.S5M1_HEFRAG_FFAR" -ENUMS.Storage.weapons.containers.kg600="weapons.containers.kg600" -ENUMS.Storage.weapons.bombs.AN_M65="weapons.bombs.AN_M65" -ENUMS.Storage.weapons.bombs.AN_M57="weapons.bombs.AN_M57" -ENUMS.Storage.weapons.bombs.BLU_3B_GROUP="weapons.bombs.BLU_3B_GROUP" -ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP-100" -ENUMS.Storage.weapons.containers.HEMTT="weapons.containers.HEMTT" -ENUMS.Storage.weapons.bombs.British_MC_500LB_Bomb_Mk1_Short="weapons.bombs.British_MC_500LB_Bomb_Mk1_Short" -ENUMS.Storage.weapons.nurs.ARAKM70BAP="weapons.nurs.ARAKM70BAP" -ENUMS.Storage.weapons.missiles.AGM_119="weapons.missiles.AGM_119" -ENUMS.Storage.weapons.missiles.MMagicII="weapons.missiles.MMagicII" -ENUMS.Storage.weapons.bombs.AB_500_1_SD_10A="weapons.bombs.AB_500_1_SD_10A" -ENUMS.Storage.weapons.nurs.HYDRA_70_M282="weapons.nurs.HYDRA_70_M282" -ENUMS.Storage.weapons.droptanks.DFT_400_GAL_A4E="weapons.droptanks.DFT_400_GAL_A4E" -ENUMS.Storage.weapons.nurs.HYDRA_70_M257="weapons.nurs.HYDRA_70_M257" -ENUMS.Storage.weapons.droptanks.AV8BNA_AERO1D="weapons.droptanks.AV8BNA_AERO1D" -ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_BLUE="weapons.containers.{US_M10_SMOKE_TANK_BLUE}" -ENUMS.Storage.weapons.nurs.ARF8M3HEI="weapons.nurs.ARF8M3HEI" -ENUMS.Storage.weapons.bombs.RN_28="weapons.bombs.RN-28" -ENUMS.Storage.weapons.bombs.Squad_30_x_Soldier_7950lb="weapons.bombs.Squad 30 x Soldier [7950lb]" -ENUMS.Storage.weapons.containers.uaz_469="weapons.containers.uaz-469" -ENUMS.Storage.weapons.containers.Otokar_Cobra="weapons.containers.Otokar_Cobra" -ENUMS.Storage.weapons.bombs.APC_BTR_82A_Air_24998lb="weapons.bombs.APC BTR-82A Air [24998lb]" -ENUMS.Storage.weapons.nurs.HYDRA_70_M274="weapons.nurs.HYDRA_70_M274" -ENUMS.Storage.weapons.missiles.P_24R="weapons.missiles.P_24R" -ENUMS.Storage.weapons.nurs.HYDRA_70_MK61="weapons.nurs.HYDRA_70_MK61" -ENUMS.Storage.weapons.missiles.Igla_1E="weapons.missiles.Igla_1E" -ENUMS.Storage.weapons.missiles.C_802AK="weapons.missiles.C-802AK" -ENUMS.Storage.weapons.nurs.C_24="weapons.nurs.C_24" -ENUMS.Storage.weapons.droptanks.M2KC_08_RPL541="weapons.droptanks.M2KC_08_RPL541" -ENUMS.Storage.weapons.nurs.C_13="weapons.nurs.C_13" -ENUMS.Storage.weapons.droptanks.droptank_110_gal="weapons.droptanks.droptank_110_gal" -ENUMS.Storage.weapons.bombs.Mk_84="weapons.bombs.Mk_84" -ENUMS.Storage.weapons.missiles.Sea_Eagle="weapons.missiles.Sea_Eagle" -ENUMS.Storage.weapons.droptanks.PTB_1200_F1="weapons.droptanks.PTB_1200_F1" -ENUMS.Storage.weapons.nurs.SNEB_TYPE256_H1="weapons.nurs.SNEB_TYPE256_H1" -ENUMS.Storage.weapons.containers.MATRA_PHIMAT="weapons.containers.MATRA-PHIMAT" -ENUMS.Storage.weapons.containers.smoke_pod="weapons.containers.smoke_pod" -ENUMS.Storage.weapons.containers.F_15E_AAQ_14_LANTIRN="weapons.containers.F-15E_AAQ-14_LANTIRN" -ENUMS.Storage.weapons.containers.EclairM_24="weapons.containers.{EclairM_24}" -ENUMS.Storage.weapons.bombs.GBU_16="weapons.bombs.GBU_16" -ENUMS.Storage.weapons.nurs.HYDRA_70_M156="weapons.nurs.HYDRA_70_M156" -ENUMS.Storage.weapons.missiles.R_60="weapons.missiles.R-60" -ENUMS.Storage.weapons.containers.zsu_23_4="weapons.containers.zsu-23-4" -ENUMS.Storage.weapons.missiles.RB75="weapons.missiles.RB75" -ENUMS.Storage.weapons.missiles.Mistral="weapons.missiles.Mistral" -ENUMS.Storage.weapons.droptanks.MB339_TT500_L="weapons.droptanks.MB339_TT500_L" -ENUMS.Storage.weapons.bombs.SAM_SA_13_STRELA_21624lb="weapons.bombs.SAM SA-13 STRELA [21624lb]" -ENUMS.Storage.weapons.bombs.SAM_Avenger_M1097_Air_7200lb="weapons.bombs.SAM Avenger M1097 Air [7200lb]" -ENUMS.Storage.weapons.droptanks.Eleven00L_Tank_Empty="weapons.droptanks.1100L Tank Empty" -ENUMS.Storage.weapons.bombs.AN_M88="weapons.bombs.AN-M88" -ENUMS.Storage.weapons.missiles.S_25L="weapons.missiles.S_25L" -ENUMS.Storage.weapons.nurs.British_AP_25LBNo1_3INCHNo1="weapons.nurs.British_AP_25LBNo1_3INCHNo1" -ENUMS.Storage.weapons.bombs.BDU_50LD="weapons.bombs.BDU_50LD" -ENUMS.Storage.weapons.bombs.AGM_62="weapons.bombs.AGM_62" -ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_WHITE="weapons.containers.{US_M10_SMOKE_TANK_WHITE}" -ENUMS.Storage.weapons.missiles.MICA_T="weapons.missiles.MICA_T" -ENUMS.Storage.weapons.containers.HVAR_rocket="weapons.containers.HVAR_rocket" -SMOKECOLOR=trigger.smokeColor -FLARECOLOR=trigger.flareColor -BIGSMOKEPRESET={ -SmallSmokeAndFire=1, -MediumSmokeAndFire=2, -LargeSmokeAndFire=3, -HugeSmokeAndFire=4, -SmallSmoke=5, -MediumSmoke=6, -LargeSmoke=7, -HugeSmoke=8, -} -DCSMAP={ -Caucasus="Caucasus", -NTTR="Nevada", -Normandy="Normandy", -PersianGulf="PersianGulf", -TheChannel="TheChannel", -Syria="Syria", -MarianaIslands="MarianaIslands", -Falklands="Falklands", -Sinai="SinaiMap" -} -CALLSIGN={ -Aircraft={ -Enfield=1, -Springfield=2, -Uzi=3, -Colt=4, -Dodge=5, -Ford=6, -Chevy=7, -Pontiac=8, -Hawg=9, -Boar=10, -Pig=11, -Tusk=12, -}, -AWACS={ -Overlord=1, -Magic=2, -Wizard=3, -Focus=4, -Darkstar=5, -}, -Tanker={ -Texaco=1, -Arco=2, -Shell=3, -Navy_One=4, -Mauler=5, -Bloodhound=6, -}, -JTAC={ -Axeman=1, -Darknight=2, -Warrior=3, -Pointer=4, -Eyeball=5, -Moonbeam=6, -Whiplash=7, -Finger=8, -Pinpoint=9, -Ferret=10, -Shaba=11, -Playboy=12, -Hammer=13, -Jaguar=14, -Deathstar=15, -Anvil=16, -Firefly=17, -Mantis=18, -Badger=19, -}, -FARP={ -London=1, -Dallas=2, -Paris=3, -Moscow=4, -Berlin=5, -Rome=6, -Madrid=7, -Warsaw=8, -Dublin=9, -Perth=10, -}, -F16={ -Viper=9, -Venom=10, -Lobo=11, -Cowboy=12, -Python=13, -Rattler=14, -Panther=15, -Wolf=16, -Weasel=17, -Wild=18, -Ninja=19, -Jedi=20, -}, -F18={ -Hornet=9, -Squid=10, -Ragin=11, -Roman=12, -Sting=13, -Jury=14, -Jokey=15, -Ram=16, -Hawk=17, -Devil=18, -Check=19, -Snake=20, -}, -F15E={ -Dude=9, -Thud=10, -Gunny=11, -Trek=12, -Sniper=13, -Sled=14, -Best=15, -Jazz=16, -Rage=17, -Tahoe=18, -}, -B1B={ -Bone=9, -Dark=10, -Vader=11 -}, -B52={ -Buff=9, -Dump=10, -Kenworth=11, -}, -TransportAircraft={ -Heavy=9, -Trash=10, -Cargo=11, -Ascot=12, -}, -} -UTILS={ -_MarkID=1 -} -UTILS.IsInstanceOf=function(object,className) -if type(className)~='string'then -if type(className)=='table'and className.IsInstanceOf~=nil then -className=className.ClassName -else -local err_str='className parameter should be a string; parameter received: '..type(className) -return false -end -end -if type(object)=='table'and object.IsInstanceOf~=nil then -return object:IsInstanceOf(className) -else -local basicDataTypes={'string','number','function','boolean','nil','table'} -for _,basicDataType in ipairs(basicDataTypes)do -if className==basicDataType then -return type(object)==basicDataType -end -end -end -return false -end -UTILS.DeepCopy=function(object) -local lookup_table={} -local function _copy(object) -if type(object)~="table"then -return object -elseif lookup_table[object]then -return lookup_table[object] -end -local new_table={} -lookup_table[object]=new_table -for index,value in pairs(object)do -new_table[_copy(index)]=_copy(value) -end -return setmetatable(new_table,getmetatable(object)) -end -local objectreturn=_copy(object) -return objectreturn -end -UTILS.OneLineSerialize=function(tbl) -lookup_table={} -local function _Serialize(tbl) -if type(tbl)=='table'then -if lookup_table[tbl]then -return lookup_table[object] -end -local tbl_str={} -lookup_table[tbl]=tbl_str -tbl_str[#tbl_str+1]='{' -for ind,val in pairs(tbl)do -local ind_str={} -if type(ind)=="number"then -ind_str[#ind_str+1]='[' -ind_str[#ind_str+1]=tostring(ind) -ind_str[#ind_str+1]=']=' -else -ind_str[#ind_str+1]='[' -ind_str[#ind_str+1]=UTILS.BasicSerialize(ind) -ind_str[#ind_str+1]=']=' -end -local val_str={} -if((type(val)=='number')or(type(val)=='boolean'))then -val_str[#val_str+1]=tostring(val) -val_str[#val_str+1]=',' -tbl_str[#tbl_str+1]=table.concat(ind_str) -tbl_str[#tbl_str+1]=table.concat(val_str) -elseif type(val)=='string'then -val_str[#val_str+1]=UTILS.BasicSerialize(val) -val_str[#val_str+1]=',' -tbl_str[#tbl_str+1]=table.concat(ind_str) -tbl_str[#tbl_str+1]=table.concat(val_str) -elseif type(val)=='nil'then -val_str[#val_str+1]='nil,' -tbl_str[#tbl_str+1]=table.concat(ind_str) -tbl_str[#tbl_str+1]=table.concat(val_str) -elseif type(val)=='table'then -if ind=="__index"then -else -val_str[#val_str+1]=_Serialize(val) -val_str[#val_str+1]=',' -tbl_str[#tbl_str+1]=table.concat(ind_str) -tbl_str[#tbl_str+1]=table.concat(val_str) -end -elseif type(val)=='function'then -tbl_str[#tbl_str+1]="f() "..tostring(ind) -tbl_str[#tbl_str+1]=',' -else -env.info('unable to serialize value type '..UTILS.BasicSerialize(type(val))..' at index '..tostring(ind)) -env.info(debug.traceback()) -end -end -tbl_str[#tbl_str+1]='}' -return table.concat(tbl_str) -else -return tostring(tbl) -end -end -local objectreturn=_Serialize(tbl) -return objectreturn -end -function UTILS._OneLineSerialize(tbl) -if type(tbl)=='table'then -local tbl_str={} -tbl_str[#tbl_str+1]='{ ' -for ind,val in pairs(tbl)do -if type(ind)=="number"then -tbl_str[#tbl_str+1]='[' -tbl_str[#tbl_str+1]=tostring(ind) -tbl_str[#tbl_str+1]='] = ' -else -tbl_str[#tbl_str+1]='[' -tbl_str[#tbl_str+1]=UTILS.BasicSerialize(ind) -tbl_str[#tbl_str+1]='] = ' -end -if((type(val)=='number')or(type(val)=='boolean'))then -tbl_str[#tbl_str+1]=tostring(val) -tbl_str[#tbl_str+1]=', ' -elseif type(val)=='string'then -tbl_str[#tbl_str+1]=UTILS.BasicSerialize(val) -tbl_str[#tbl_str+1]=', ' -elseif type(val)=='nil'then -tbl_str[#tbl_str+1]='nil, ' -elseif type(val)=='table'then -else -end -end -tbl_str[#tbl_str+1]='}' -return table.concat(tbl_str) -else -return UTILS.BasicSerialize(tbl) -end -end -UTILS.BasicSerialize=function(s) -if s==nil then -return"\"\"" -else -if((type(s)=='number')or(type(s)=='boolean')or(type(s)=='function')or(type(s)=='userdata'))then -return tostring(s) -elseif type(s)=="table"then -return UTILS._OneLineSerialize(s) -elseif type(s)=='string'then -s=string.format('(%s)',s) -return s -end -end -end -function UTILS.PrintTableToLog(table,indent) -local text="\n" -if not table then -env.warning("No table passed!") -return nil -end -if not indent then indent=0 end -for k,v in pairs(table)do -if string.find(k," ")then k='"'..k..'"'end -if type(v)=="table"then -env.info(string.rep(" ",indent)..tostring(k).." = {") -text=text..string.rep(" ",indent)..tostring(k).." = {\n" -text=text..tostring(UTILS.PrintTableToLog(v,indent+1)).."\n" -env.info(string.rep(" ",indent).."},") -text=text..string.rep(" ",indent).."},\n" -else -local value -if tostring(v)=="true"or tostring(v)=="false"or tonumber(v)~=nil then -value=v -else -value='"'..tostring(v)..'"' -end -env.info(string.rep(" ",indent)..tostring(k).." = "..tostring(value)..",\n") -text=text..string.rep(" ",indent)..tostring(k).." = "..tostring(value)..",\n" -end -end -return text -end -function UTILS.TableShow(tbl,loc,indent,tableshow_tbls) -tableshow_tbls=tableshow_tbls or{} -loc=loc or"" -indent=indent or"" -if type(tbl)=='table'then -tableshow_tbls[tbl]=loc -local tbl_str={} -tbl_str[#tbl_str+1]=indent..'{\n' -for ind,val in pairs(tbl)do -if type(ind)=="number"then -tbl_str[#tbl_str+1]=indent -tbl_str[#tbl_str+1]=loc..'[' -tbl_str[#tbl_str+1]=tostring(ind) -tbl_str[#tbl_str+1]='] = ' -else -tbl_str[#tbl_str+1]=indent -tbl_str[#tbl_str+1]=loc..'[' -tbl_str[#tbl_str+1]=UTILS.BasicSerialize(ind) -tbl_str[#tbl_str+1]='] = ' -end -if((type(val)=='number')or(type(val)=='boolean'))then -tbl_str[#tbl_str+1]=tostring(val) -tbl_str[#tbl_str+1]=',\n' -elseif type(val)=='string'then -tbl_str[#tbl_str+1]=UTILS.BasicSerialize(val) -tbl_str[#tbl_str+1]=',\n' -elseif type(val)=='nil'then -tbl_str[#tbl_str+1]='nil,\n' -elseif type(val)=='table'then -if tableshow_tbls[val]then -tbl_str[#tbl_str+1]=tostring(val)..' already defined: '..tableshow_tbls[val]..',\n' -else -tableshow_tbls[val]=loc..'['..UTILS.BasicSerialize(ind)..']' -tbl_str[#tbl_str+1]=tostring(val)..' ' -tbl_str[#tbl_str+1]=UTILS.TableShow(val,loc..'['..UTILS.BasicSerialize(ind)..']',indent..' ',tableshow_tbls) -tbl_str[#tbl_str+1]=',\n' -end -elseif type(val)=='function'then -if debug and debug.getinfo then -local fcnname=tostring(val) -local info=debug.getinfo(val,"S") -if info.what=="C"then -tbl_str[#tbl_str+1]=string.format('%q',fcnname..', C function')..',\n' -else -if(string.sub(info.source,1,2)==[[./]])then -tbl_str[#tbl_str+1]=string.format('%q',fcnname..', defined in ('..info.linedefined..'-'..info.lastlinedefined..')'..info.source)..',\n' -else -tbl_str[#tbl_str+1]=string.format('%q',fcnname..', defined in ('..info.linedefined..'-'..info.lastlinedefined..')')..',\n' -end -end -else -tbl_str[#tbl_str+1]='a function,\n' -end -else -tbl_str[#tbl_str+1]='unable to serialize value type '..UTILS.BasicSerialize(type(val))..' at index '..tostring(ind) -end -end -tbl_str[#tbl_str+1]=indent..'}' -return table.concat(tbl_str) -end -end -function UTILS.Gdump(fname) -if lfs and io then -local fdir=lfs.writedir()..[[Logs\]]..fname -local f=io.open(fdir,'w') -f:write(UTILS.TableShow(_G)) -f:close() -env.info(string.format('Wrote debug data to $1',fdir)) -else -env.error("WARNING: lfs and/or io not de-sanitized - cannot dump _G!") -end -end -function UTILS.DoString(s) -local f,err=loadstring(s) -if f then -return true,f() -else -return false,err -end -end -UTILS.ToDegree=function(angle) -return angle*180/math.pi -end -UTILS.ToRadian=function(angle) -return angle*math.pi/180 -end -UTILS.MetersToNM=function(meters) -return meters/1852 -end -UTILS.KiloMetersToNM=function(kilometers) -return kilometers/1852*1000 -end -UTILS.MetersToSM=function(meters) -return meters/1609.34 -end -UTILS.KiloMetersToSM=function(kilometers) -return kilometers/1609.34*1000 -end -UTILS.MetersToFeet=function(meters) -return meters/0.3048 -end -UTILS.KiloMetersToFeet=function(kilometers) -return kilometers/0.3048*1000 -end -UTILS.NMToMeters=function(NM) -return NM*1852 -end -UTILS.NMToKiloMeters=function(NM) -return NM*1852/1000 -end -UTILS.FeetToMeters=function(feet) -return feet*0.3048 -end -UTILS.KnotsToKmph=function(knots) -return knots*1.852 -end -UTILS.KmphToKnots=function(knots) -return knots/1.852 -end -UTILS.KmphToMps=function(kmph) -return kmph/3.6 -end -UTILS.MpsToKmph=function(mps) -return mps*3.6 -end -UTILS.MiphToMps=function(miph) -return miph*0.44704 -end -UTILS.MpsToMiph=function(mps) -return mps/0.44704 -end -UTILS.MpsToKnots=function(mps) -return mps*1.94384 -end -UTILS.KnotsToMps=function(knots) -if type(knots)=="number"then -return knots/1.94384 -else -return 0 -end -end -UTILS.CelsiusToFahrenheit=function(Celcius) -return Celcius*9/5+32 -end -UTILS.hPa2inHg=function(hPa) -return hPa*0.0295299830714 -end -UTILS.IasToTas=function(ias,altitude,oatcorr) -oatcorr=oatcorr or 0.017 -local tas=ias+(ias*oatcorr*UTILS.MetersToFeet(altitude)/1000) -return tas -end -UTILS.TasToIas=function(tas,altitude,oatcorr) -oatcorr=oatcorr or 0.017 -local ias=tas/(1+oatcorr*UTILS.MetersToFeet(altitude)/1000) -return ias -end -UTILS.KnotsToAltKIAS=function(knots,altitude) -return(knots*0.018*(altitude/1000))+knots -end -UTILS.hPa2mmHg=function(hPa) -return hPa*0.7500615613030 -end -UTILS.kg2lbs=function(kg) -return kg*2.20462 -end -UTILS.tostringLL=function(lat,lon,acc,DMS) -local latHemi,lonHemi -if lat>0 then -latHemi='N' -else -latHemi='S' -end -if lon>0 then -lonHemi='E' -else -lonHemi='W' -end -lat=math.abs(lat) -lon=math.abs(lon) -local latDeg=math.floor(lat) -local latMin=(lat-latDeg)*60 -local lonDeg=math.floor(lon) -local lonMin=(lon-lonDeg)*60 -if DMS then -local oldLatMin=latMin -latMin=math.floor(latMin) -local latSec=UTILS.Round((oldLatMin-latMin)*60,acc) -local oldLonMin=lonMin -lonMin=math.floor(lonMin) -local lonSec=UTILS.Round((oldLonMin-lonMin)*60,acc) -if latSec==60 then -latSec=0 -latMin=latMin+1 -end -if lonSec==60 then -lonSec=0 -lonMin=lonMin+1 -end -local secFrmtStr -secFrmtStr='%02d' -if acc<=0 then -secFrmtStr='%02d' -else -local width=3+acc -secFrmtStr='%0'..width..'.'..acc..'f' -end -return string.format('%03d°',latDeg)..string.format('%02d',latMin)..'\''..string.format(secFrmtStr,latSec)..'"'..latHemi..' ' -..string.format('%03d°',lonDeg)..string.format('%02d',lonMin)..'\''..string.format(secFrmtStr,lonSec)..'"'..lonHemi -else -latMin=UTILS.Round(latMin,acc) -lonMin=UTILS.Round(lonMin,acc) -if latMin==60 then -latMin=0 -latDeg=latDeg+1 -end -if lonMin==60 then -lonMin=0 -lonDeg=lonDeg+1 -end -local minFrmtStr -if acc<=0 then -minFrmtStr='%02d' -else -local width=3+acc -minFrmtStr='%0'..width..'.'..acc..'f' -end -return string.format('%03d°',latDeg)..' '..string.format(minFrmtStr,latMin)..'\''..latHemi..' ' -..string.format('%03d°',lonDeg)..' '..string.format(minFrmtStr,lonMin)..'\''..lonHemi -end -end -UTILS.tostringLLM2KData=function(lat,lon,acc) -local latHemi,lonHemi -if lat>0 then -latHemi='N' -else -latHemi='S' -end -if lon>0 then -lonHemi='E' -else -lonHemi='W' -end -lat=math.abs(lat) -lon=math.abs(lon) -local latDeg=math.floor(lat) -local latMin=(lat-latDeg)*60 -local lonDeg=math.floor(lon) -local lonMin=(lon-lonDeg)*60 -latMin=UTILS.Round(latMin,acc) -lonMin=UTILS.Round(lonMin,acc) -if latMin==60 then -latMin=0 -latDeg=latDeg+1 -end -if lonMin==60 then -lonMin=0 -lonDeg=lonDeg+1 -end -local minFrmtStr -if acc<=0 then -minFrmtStr='%02d' -else -local width=3+acc -minFrmtStr='%0'..width..'.'..acc..'f' -end -return latHemi..string.format('%02d:',latDeg)..string.format(minFrmtStr,latMin),lonHemi..string.format('%02d:',lonDeg)..string.format(minFrmtStr,lonMin) -end -UTILS.tostringMGRS=function(MGRS,acc) -if acc<=0 then -return MGRS.UTMZone..' '..MGRS.MGRSDigraph -else -if acc>5 then acc=5 end -local Easting=tostring(MGRS.Easting) -local Northing=tostring(MGRS.Northing) -local nE=5-string.len(Easting) -local nN=5-string.len(Northing) -for i=1,nE do Easting="0"..Easting end -for i=1,nN do Northing="0"..Northing end -return string.format("%s %s %s %s",MGRS.UTMZone,MGRS.MGRSDigraph,string.sub(Easting,1,acc),string.sub(Northing,1,acc)) -end -end -function UTILS.Round(num,idp) -local mult=10^(idp or 0) -return math.floor(num*mult+0.5)/mult -end -function UTILS.DoString(s) -local f,err=loadstring(s) -if f then -return true,f() -else -return false,err -end -end -function UTILS.spairs(t,order) -local keys={} -for k in pairs(t)do keys[#keys+1]=k end -if order then -table.sort(keys,function(a,b)return order(t,a,b)end) -else -table.sort(keys) -end -local i=0 -return function() -i=i+1 -if keys[i]then -return keys[i],t[keys[i]] -end -end -end -function UTILS.kpairs(t,getkey,order) -local keys={} -local keyso={} -for k,o in pairs(t)do keys[#keys+1]=k keyso[#keyso+1]=getkey(o)end -if order then -table.sort(keys,function(a,b)return order(t,a,b)end) -else -table.sort(keys) -end -local i=0 -return function() -i=i+1 -if keys[i]then -return keyso[i],t[keys[i]] -end -end -end -function UTILS.rpairs(t) -local keys={} -for k in pairs(t)do keys[#keys+1]=k end -local random={} -local j=#keys -for i=1,j do -local k=math.random(1,#keys) -random[i]=keys[k] -table.remove(keys,k) -end -local i=0 -return function() -i=i+1 -if random[i]then -return random[i],t[random[i]] -end -end -end -function UTILS.GetMarkID() -UTILS._MarkID=UTILS._MarkID+1 -return UTILS._MarkID -end -function UTILS.RemoveMark(MarkID,Delay) -if Delay and Delay>0 then -TIMER:New(UTILS.RemoveMark,MarkID):Start(Delay) -else -if MarkID then -trigger.action.removeMark(MarkID) -end -end -end -function UTILS.IsInRadius(InVec2,Vec2,Radius) -local InRadius=((InVec2.x-Vec2.x)^2+(InVec2.y-Vec2.y)^2)^0.5<=Radius -return InRadius -end -function UTILS.IsInSphere(InVec3,Vec3,Radius) -local InSphere=((InVec3.x-Vec3.x)^2+(InVec3.y-Vec3.y)^2+(InVec3.z-Vec3.z)^2)^0.5<=Radius -return InSphere -end -function UTILS.BeaufortScale(speed) -local bn=nil -local bd=nil -if speed<0.51 then -bn=0 -bd="Calm" -elseif speed<2.06 then -bn=1 -bd="Light Air" -elseif speed<3.60 then -bn=2 -bd="Light Breeze" -elseif speed<5.66 then -bn=3 -bd="Gentle Breeze" -elseif speed<8.23 then -bn=4 -bd="Moderate Breeze" -elseif speed<11.32 then -bn=5 -bd="Fresh Breeze" -elseif speed<14.40 then -bn=6 -bd="Strong Breeze" -elseif speed<17.49 then -bn=7 -bd="Moderate Gale" -elseif speed<21.09 then -bn=8 -bd="Fresh Gale" -elseif speed<24.69 then -bn=9 -bd="Strong Gale" -elseif speed<28.81 then -bn=10 -bd="Storm" -elseif speed<32.92 then -bn=11 -bd="Violent Storm" -else -bn=12 -bd="Hurricane" -end -return bn,bd -end -function UTILS.Split(str,sep) -local result={} -local regex=("([^%s]+)"):format(sep) -for each in str:gmatch(regex)do -table.insert(result,each) -end -return result -end -function UTILS.GetCharacters(str) -local chars={} -for i=1,#str do -local c=str:sub(i,i) -table.insert(chars,c) -end -return chars -end -function UTILS.SecondsToClock(seconds,short) -if seconds==nil then -return nil -end -local seconds=tonumber(seconds) -local _seconds=seconds%(60*60*24) -if seconds<0 then -return nil -else -local hours=string.format("%02.f",math.floor(_seconds/3600)) -local mins=string.format("%02.f",math.floor(_seconds/60-(hours*60))) -local secs=string.format("%02.f",math.floor(_seconds-hours*3600-mins*60)) -local days=string.format("%d",seconds/(60*60*24)) -local clock=hours..":"..mins..":"..secs.."+"..days -if short then -if hours=="00"then -clock=hours..":"..mins..":"..secs -else -clock=hours..":"..mins..":"..secs -end -end -return clock -end -end -function UTILS.SecondsOfToday() -local time=timer.getAbsTime() -local clock=UTILS.SecondsToClock(time,true) -return UTILS.ClockToSeconds(clock) -end -function UTILS.SecondsToMidnight() -return 24*60*60-UTILS.SecondsOfToday() -end -function UTILS.ClockToSeconds(clock) -if clock==nil then -return nil -end -local seconds=0 -local dsplit=UTILS.Split(clock,"+") -if#dsplit>1 then -seconds=seconds+tonumber(dsplit[2])*60*60*24 -end -local tsplit=UTILS.Split(dsplit[1],":") -local i=1 -for _,time in ipairs(tsplit)do -if i==1 then -seconds=seconds+tonumber(time)*60*60 -elseif i==2 then -seconds=seconds+tonumber(time)*60 -elseif i==3 then -seconds=seconds+tonumber(time) -end -i=i+1 -end -return seconds -end -function UTILS.DisplayMissionTime(duration) -duration=duration or 5 -local Tnow=timer.getAbsTime() -local mission_time=Tnow-timer.getTime0() -local mission_time_minutes=mission_time/60 -local mission_time_seconds=mission_time%60 -local local_time=UTILS.SecondsToClock(Tnow) -local text=string.format("Time: %s - %02d:%02d",local_time,mission_time_minutes,mission_time_seconds) -MESSAGE:New(text,duration):ToAll() -end -function UTILS.ReplaceIllegalCharacters(Text,ReplaceBy) -ReplaceBy=ReplaceBy or"_" -local text=Text:gsub("[<>|/?*:\\]",ReplaceBy) -return text -end -function UTILS.RandomGaussian(x0,sigma,xmin,xmax,imax) -sigma=sigma or 10 -imax=imax or 100 -local r -local gotit=false -local i=0 -while not gotit do -local x1=math.random() -local x2=math.random() -r=math.sqrt(-2*sigma*sigma*math.log(x1))*math.cos(2*math.pi*x2)+x0 -i=i+1 -if(r>=xmin and r<=xmax)or i>imax then -gotit=true -end -end -return r -end -function UTILS.Randomize(value,fac,lower,upper) -local min -if lower then -min=math.max(value-value*fac,lower) -else -min=value-value*fac -end -local max -if upper then -max=math.min(value+value*fac,upper) -else -max=value+value*fac -end -local r=math.random(min,max) -return r -end -function UTILS.VecDot(a,b) -return a.x*b.x+a.y*b.y+a.z*b.z -end -function UTILS.Vec2Dot(a,b) -return a.x*b.x+a.y*b.y -end -function UTILS.VecNorm(a) -return math.sqrt(UTILS.VecDot(a,a)) -end -function UTILS.Vec2Norm(a) -return math.sqrt(UTILS.Vec2Dot(a,a)) -end -function UTILS.VecDist2D(a,b) -local d=math.huge -if(not a)or(not b)then return d end -local c={x=b.x-a.x,y=b.y-a.y} -d=math.sqrt(c.x*c.x+c.y*c.y) -return d -end -function UTILS.VecDist3D(a,b) -local d=math.huge -if(not a)or(not b)then return d end -local c={x=b.x-a.x,y=b.y-a.y,z=b.z-a.z} -d=math.sqrt(UTILS.VecDot(c,c)) -return d -end -function UTILS.VecCross(a,b) -return{x=a.y*b.z-a.z*b.y,y=a.z*b.x-a.x*b.z,z=a.x*b.y-a.y*b.x} -end -function UTILS.VecSubstract(a,b) -return{x=a.x-b.x,y=a.y-b.y,z=a.z-b.z} -end -function UTILS.VecSubtract(a,b) -return UTILS.VecSubstract(a,b) -end -function UTILS.Vec2Substract(a,b) -return{x=a.x-b.x,y=a.y-b.y} -end -function UTILS.Vec2Subtract(a,b) -return UTILS.Vec2Substract(a,b) -end -function UTILS.VecAdd(a,b) -return{x=a.x+b.x,y=a.y+b.y,z=a.z+b.z} -end -function UTILS.Vec2Add(a,b) -return{x=a.x+b.x,y=a.y+b.y} -end -function UTILS.VecAngle(a,b) -local cosalpha=UTILS.VecDot(a,b)/(UTILS.VecNorm(a)*UTILS.VecNorm(b)) -local alpha=0 -if cosalpha>=0.9999999999 then -alpha=0 -elseif cosalpha<=-0.999999999 then -alpha=math.pi -else -alpha=math.acos(cosalpha) -end -return math.deg(alpha) -end -function UTILS.VecHdg(a) -local h=math.deg(math.atan2(a.z,a.x)) -if h<0 then -h=h+360 -end -return h -end -function UTILS.Vec2Hdg(a) -local h=math.deg(math.atan2(a.y,a.x)) -if h<0 then -h=h+360 -end -return h -end -function UTILS.HdgDiff(h1,h2) -local alpha=math.rad(tonumber(h1)) -local beta=math.rad(tonumber(h2)) -local v1={x=math.cos(alpha),y=0,z=math.sin(alpha)} -local v2={x=math.cos(beta),y=0,z=math.sin(beta)} -local delta=UTILS.VecAngle(v1,v2) -return math.abs(delta) -end -function UTILS.HdgTo(a,b) -local dz=b.z-a.z -local dx=b.x-a.x -local heading=math.deg(math.atan2(dz,dx)) -if heading<0 then -heading=360+heading -end -return heading -end -function UTILS.VecTranslate(a,distance,angle) -local SX=a.x -local SY=a.z -local Radians=math.rad(angle or 0) -local TX=distance*math.cos(Radians)+SX -local TY=distance*math.sin(Radians)+SY -return{x=TX,y=a.y,z=TY} -end -function UTILS.Vec2Translate(a,distance,angle) -local SX=a.x -local SY=a.y -local Radians=math.rad(angle or 0) -local TX=distance*math.cos(Radians)+SX -local TY=distance*math.sin(Radians)+SY -return{x=TX,y=TY} -end -function UTILS.Rotate2D(a,angle) -local phi=math.rad(angle) -local x=a.z -local y=a.x -local Z=x*math.cos(phi)-y*math.sin(phi) -local X=x*math.sin(phi)+y*math.cos(phi) -local Y=a.y -local A={x=X,y=Y,z=Z} -return A -end -function UTILS.Vec2Rotate2D(a,angle) -local phi=math.rad(angle) -local x=a.x -local y=a.y -local X=x*math.cos(phi)-y*math.sin(phi) -local Y=x*math.sin(phi)+y*math.cos(phi) -local A={x=X,y=Y} -return A -end -function UTILS.TACANToFrequency(TACANChannel,TACANMode) -if type(TACANChannel)~="number"then -return nil -end -if TACANMode~="X"and TACANMode~="Y"then -return nil -end -local A=1151 -local B=64 -if TACANChannel<64 then -B=1 -end -if TACANMode=='Y'then -A=1025 -if TACANChannel<64 then -A=1088 -end -else -if TACANChannel<64 then -A=962 -end -end -return(A+TACANChannel-B)*1000000 -end -function UTILS.GetDCSMap() -return env.mission.theatre -end -function UTILS.GetDCSMissionDate() -local year=tostring(env.mission.date.Year) -local month=tostring(env.mission.date.Month) -local day=tostring(env.mission.date.Day) -return string.format("%s/%s/%s",year,month,day),tonumber(year),tonumber(month),tonumber(day) -end -function UTILS.GetMissionDay(Time) -Time=Time or timer.getAbsTime() -local clock=UTILS.SecondsToClock(Time,false) -local x=tonumber(UTILS.Split(clock,"+")[2]) -return x -end -function UTILS.GetMissionDayOfYear(Time) -local Date,Year,Month,Day=UTILS.GetDCSMissionDate() -local d=UTILS.GetMissionDay(Time) -return UTILS.GetDayOfYear(Year,Month,Day)+d -end -function UTILS.GetMagneticDeclination(map) -map=map or UTILS.GetDCSMap() -local declination=0 -if map==DCSMAP.Caucasus then -declination=6 -elseif map==DCSMAP.NTTR then -declination=12 -elseif map==DCSMAP.Normandy then -declination=-10 -elseif map==DCSMAP.PersianGulf then -declination=2 -elseif map==DCSMAP.TheChannel then -declination=-10 -elseif map==DCSMAP.Syria then -declination=5 -elseif map==DCSMAP.MarianaIslands then -declination=2 -elseif map==DCSMAP.Falklands then -declination=12 -elseif map==DCSMAP.Sinai then -declination=4.8 -else -declination=0 -end -return declination -end -function UTILS.FileExists(file) -if io then -local f=io.open(file,"r") -if f~=nil then -io.close(f) -return true -else -return false -end -else -return nil -end -end -function UTILS.CheckMemory(output) -local time=timer.getTime() -local clock=UTILS.SecondsToClock(time) -local mem=collectgarbage("count") -if output then -env.info(string.format("T=%s Memory usage %d kByte = %.2f MByte",clock,mem,mem/1024)) -end -return mem -end -function UTILS.GetCoalitionName(Coalition) -if Coalition then -if Coalition==coalition.side.BLUE then -return"Blue" -elseif Coalition==coalition.side.RED then -return"Red" -elseif Coalition==coalition.side.NEUTRAL then -return"Neutral" -else -return"Unknown" -end -else -return"Unknown" -end -end -function UTILS.GetCoalitionEnemy(Coalition,Neutral) -local Coalitions={} -if Coalition then -if Coalition==coalition.side.RED then -Coalitions={coalition.side.BLUE} -elseif Coalition==coalition.side.BLUE then -Coalitions={coalition.side.RED} -elseif Coalition==coalition.side.NEUTRAL then -Coalitions={coalition.side.RED,coalition.side.BLUE} -end -end -if Neutral then -table.insert(Coalitions,coalition.side.NEUTRAL) -end -return Coalitions -end -function UTILS.GetModulationName(Modulation) -if Modulation then -if Modulation==0 then -return"AM" -elseif Modulation==1 then -return"FM" -else -return"Unknown" -end -else -return"Unknown" -end -end -function UTILS.GetReportingName(Typename) -local typename=string.lower(Typename) -for name,value in pairs(ENUMS.ReportingName.NATO)do -local svalue=string.lower(value) -if string.find(typename,svalue,1,true)then -return name -end -end -return"Bogey" -end -function UTILS.GetCallsignName(Callsign) -for name,value in pairs(CALLSIGN.Aircraft)do -if value==Callsign then -return name -end -end -for name,value in pairs(CALLSIGN.AWACS)do -if value==Callsign then -return name -end -end -for name,value in pairs(CALLSIGN.JTAC)do -if value==Callsign then -return name -end -end -for name,value in pairs(CALLSIGN.Tanker)do -if value==Callsign then -return name -end -end -for name,value in pairs(CALLSIGN.B1B)do -if value==Callsign then -return name -end -end -for name,value in pairs(CALLSIGN.B52)do -if value==Callsign then -return name -end -end -for name,value in pairs(CALLSIGN.F15E)do -if value==Callsign then -return name -end -end -for name,value in pairs(CALLSIGN.F16)do -if value==Callsign then -return name -end -end -for name,value in pairs(CALLSIGN.F18)do -if value==Callsign then -return name -end -end -for name,value in pairs(CALLSIGN.FARP)do -if value==Callsign then -return name -end -end -for name,value in pairs(CALLSIGN.TransportAircraft)do -if value==Callsign then -return name -end -end -return"Ghostrider" -end -function UTILS.GMTToLocalTimeDifference() -local theatre=UTILS.GetDCSMap() -if theatre==DCSMAP.Caucasus then -return 4 -elseif theatre==DCSMAP.PersianGulf then -return 4 -elseif theatre==DCSMAP.NTTR then -return-8 -elseif theatre==DCSMAP.Normandy then -return 0 -elseif theatre==DCSMAP.TheChannel then -return 2 -elseif theatre==DCSMAP.Syria then -return 3 -elseif theatre==DCSMAP.MarianaIslands then -return 10 -elseif theatre==DCSMAP.Falklands then -return-3 -elseif theatre==DCSMAP.Sinai then -return 2 -else -BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0",tostring(theatre))) -return 0 -end -end -function UTILS.GetDayOfYear(Year,Month,Day) -local floor=math.floor -local n1=floor(275*Month/9) -local n2=floor((Month+9)/12) -local n3=(1+floor((Year-4*floor(Year/4)+2)/3)) -return n1-(n2*n3)+Day-30 -end -function UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,Rising,Tlocal) -local zenith=90.83 -local latitude=Latitude -local longitude=Longitude -local rising=Rising -local n=DayOfYear -Tlocal=Tlocal or 0 -local rad=math.rad -local deg=math.deg -local floor=math.floor -local frac=function(n)return n-floor(n)end -local cos=function(d)return math.cos(rad(d))end -local acos=function(d)return deg(math.acos(d))end -local sin=function(d)return math.sin(rad(d))end -local asin=function(d)return deg(math.asin(d))end -local tan=function(d)return math.tan(rad(d))end -local atan=function(d)return deg(math.atan(d))end -local function fit_into_range(val,min,max) -local range=max-min -local count -if val=max then -count=floor((val-max)/range)+1 -return val-count*range -else -return val -end -end -local lng_hour=longitude/15 -local t -if rising then -t=n+((6-lng_hour)/24) -else -t=n+((18-lng_hour)/24) -end -local M=(0.9856*t)-3.289 -local L=fit_into_range(M+(1.916*sin(M))+(0.020*sin(2*M))+282.634,0,360) -local RA=fit_into_range(atan(0.91764*tan(L)),0,360) -local Lquadrant=floor(L/90)*90 -local RAquadrant=floor(RA/90)*90 -RA=RA+Lquadrant-RAquadrant -RA=RA/15 -local sinDec=0.39782*sin(L) -local cosDec=cos(asin(sinDec)) -local cosH=(cos(zenith)-(sinDec*sin(latitude)))/(cosDec*cos(latitude)) -if rising and cosH>1 then -return"N/R" -elseif cosH<-1 then -return"N/S" -end -local H -if rising then -H=360-acos(cosH) -else -H=acos(cosH) -end -H=H/15 -local T=H+RA-(0.06571*t)-6.622 -local UT=fit_into_range(T-lng_hour+Tlocal,0,24) -return floor(UT)*60*60+frac(UT)*60*60 -end -function UTILS.GetSunrise(Day,Month,Year,Latitude,Longitude,Tlocal) -local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day) -return UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tlocal) -end -function UTILS.GetSunset(Day,Month,Year,Latitude,Longitude,Tlocal) -local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day) -return UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tlocal) -end -function UTILS.GetOSTime() -if os then -local ts=0 -local t=os.date("*t") -local s=t.sec -local m=t.min*60 -local h=t.hour*3600 -ts=s+m+h -return ts -else -return nil -end -end -function UTILS.ShuffleTable(t) -if t==nil or type(t)~="table"then -BASE:I("Error in ShuffleTable: Missing or wrong type of Argument") -return -end -math.random() -math.random() -math.random() -local TempTable={} -for i=1,#t do -local r=math.random(1,#t) -TempTable[i]=t[r] -table.remove(t,r) -end -return TempTable -end -function UTILS.GetRandomTableElement(t,replace) -if t==nil or type(t)~="table"then -BASE:I("Error in ShuffleTable: Missing or wrong type of Argument") -return -end -math.random() -math.random() -math.random() -local r=math.random(#t) -local element=t[r] -if not replace then -table.remove(t,r) -end -return element -end -function UTILS.IsLoadingDoorOpen(unit_name) -local unit=Unit.getByName(unit_name) -if unit~=nil then -local type_name=unit:getTypeName() -BASE:T("TypeName = "..type_name) -if type_name=="Mi-8MT"and(unit:getDrawArgumentValue(38)==1 or unit:getDrawArgumentValue(86)==1 or unit:getDrawArgumentValue(250)<0)then -BASE:T(unit_name.." Cargo doors are open or cargo door not present") -return true -end -if type_name=="Mi-24P"and(unit:getDrawArgumentValue(38)==1 or unit:getDrawArgumentValue(86)==1)then -BASE:T(unit_name.." a side door is open") -return true -end -if type_name=="UH-1H"and(unit:getDrawArgumentValue(43)==1 or unit:getDrawArgumentValue(44)==1)then -BASE:T(unit_name.." a side door is open ") -return true -end -if string.find(type_name,"SA342")and(unit:getDrawArgumentValue(34)==1)then -BASE:T(unit_name.." front door(s) are open or doors removed") -return true -end -if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1215)==1 and unit:getDrawArgumentValue(1216)==1)then -BASE:T(unit_name.." rear doors are open") -return true -end -if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1220)==1 or unit:getDrawArgumentValue(1221)==1)then -BASE:T(unit_name.." para doors are open") -return true -end -if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1217)==1)then -BASE:T(unit_name.." side door is open") -return true -end -if type_name=="Bell-47"then -BASE:T(unit_name.." door is open") -return true -end -if type_name=="UH-60L"and(unit:getDrawArgumentValue(401)==1 or unit:getDrawArgumentValue(402)==1)then -BASE:T(unit_name.." cargo door is open") -return true -end -if type_name=="UH-60L"and(unit:getDrawArgumentValue(38)>0 or unit:getDrawArgumentValue(400)==1)then -BASE:T(unit_name.." front door(s) are open") -return true -end -if type_name=="AH-64D_BLK_II"then -BASE:T(unit_name.." front door(s) are open") -return true -end -if type_name=="Bronco-OV-10A"then -BASE:T(unit_name.." front door(s) are open") -return true -end -return false -end -return nil -end -function UTILS.GenerateFMFrequencies() -local FreeFMFrequencies={} -for _first=3,7 do -for _second=0,5 do -for _third=0,9 do -local _frequency=((100*_first)+(10*_second)+_third)*100000 -table.insert(FreeFMFrequencies,_frequency) -end -end -end -return FreeFMFrequencies -end -function UTILS.GenerateVHFrequencies() -local _skipFrequencies={ -214,274,291.5,295,297.5, -300.5,304,305,307,309.5,311,312,312.5,316, -320,324,328,329,330,332,336,337, -342,343,348,351,352,353,358, -363,365,368,372.5,374, -380,381,384,385,389,395,396, -414,420,430,432,435,440,450,455,462,470,485, -507,515,520,525,528,540,550,560,570,577,580, -602,625,641,662,670,680,682,690, -705,720,722,730,735,740,745,750,770,795, -822,830,862,866, -905,907,920,935,942,950,995, -1000,1025,1030,1050,1065,1116,1175,1182,1210,1215 -} -local FreeVHFFrequencies={} -local _start=200000 -while _start<400000 do -local _found=false -for _,value in pairs(_skipFrequencies)do -if value*1000==_start then -_found=true -break -end -end -if _found==false then -table.insert(FreeVHFFrequencies,_start) -end -_start=_start+10000 -end -_start=400000 -while _start<850000 do -local _found=false -for _,value in pairs(_skipFrequencies)do -if value*1000==_start then -_found=true -break -end -end -if _found==false then -table.insert(FreeVHFFrequencies,_start) -end -_start=_start+10000 -end -_start=850000 -while _start<=999000 do -local _found=false -for _,value in pairs(_skipFrequencies)do -if value*1000==_start then -_found=true -break -end -end -if _found==false then -table.insert(FreeVHFFrequencies,_start) -end -_start=_start+50000 -end -return FreeVHFFrequencies -end -function UTILS.GenerateUHFrequencies(Start,End) -local FreeUHFFrequencies={} -local _start=220000000 -if not Start then -while _start<399000000 do -if _start~=243000000 then -table.insert(FreeUHFFrequencies,_start) -end -_start=_start+500000 -end -else -local myend=End*1000000 or 399000000 -local mystart=Start*1000000 or 220000000 -while _start<399000000 do -if _start~=243000000 and(_startmyend)then -print(_start) -table.insert(FreeUHFFrequencies,_start) -end -_start=_start+500000 -end -end -return FreeUHFFrequencies -end -function UTILS.GenerateLaserCodes() -local jtacGeneratedLaserCodes={} -local function ContainsDigit(_number,_numberToFind) -local _thisNumber=_number -local _thisDigit=0 -while _thisNumber~=0 do -_thisDigit=_thisNumber%10 -_thisNumber=math.floor(_thisNumber/10) -if _thisDigit==_numberToFind then -return true -end -end -return false -end -local _code=1111 -local _count=1 -while _code<1777 and _count<30 do -while true do -_code=_code+1 -if not ContainsDigit(_code,8) -and not ContainsDigit(_code,9) -and not ContainsDigit(_code,0)then -table.insert(jtacGeneratedLaserCodes,_code) -break -end -end -_count=_count+1 -end -return jtacGeneratedLaserCodes -end -function UTILS.EnsureTable(Object,ReturnNil) -if Object then -if type(Object)~="table"then -Object={Object} -end -else -if ReturnNil then -return nil -else -Object={} -end -end -return Object -end -function UTILS.SaveToFile(Path,Filename,Data) -if not io then -BASE:E("ERROR: io not desanitized. Can't save current file.") -return false -end -if Path==nil and not lfs then -BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") -end -local path=nil -if lfs then -path=Path or lfs.writedir() -end -local filename=Filename -if path~=nil then -filename=path.."\\"..filename -end -local f=assert(io.open(filename,"wb")) -f:write(Data) -f:close() -return true -end -function UTILS.LoadFromFile(Path,Filename) -if not io then -BASE:E("ERROR: io not desanitized. Can't save current state.") -return false -end -if Path==nil and not lfs then -BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") -end -local path=nil -if lfs then -path=Path or lfs.writedir() -end -local filename=Filename -if path~=nil then -filename=path.."\\"..filename -end -local exists=UTILS.CheckFileExists(Path,Filename) -if not exists then -BASE:I(string.format("ERROR: File %s does not exist!",filename)) -return false -end -local file=assert(io.open(filename,"rb")) -local loadeddata={} -for line in file:lines()do -loadeddata[#loadeddata+1]=line -end -file:close() -return true,loadeddata -end -function UTILS.CheckFileExists(Path,Filename) -local function _fileexists(name) -local f=io.open(name,"r") -if f~=nil then -io.close(f) -return true -else -return false -end -end -if not io then -BASE:E("ERROR: io not desanitized.") -return false -end -if Path==nil and not lfs then -BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") -end -local path=nil -if lfs then -path=Path or lfs.writedir() -end -local filename=Filename -if path~=nil then -filename=path.."\\"..filename -end -local exists=_fileexists(filename) -if not exists then -BASE:E(string.format("ERROR: File %s does not exist!",filename)) -return false -else -return true -end -end -function UTILS.GetCountPerTypeName(Group) -local units=Group:GetUnits() -local TypeNameTable={} -for _,_unt in pairs(units)do -local unit=_unt -local typen=unit:GetTypeName() -if not TypeNameTable[typen]then -TypeNameTable[typen]=1 -else -TypeNameTable[typen]=TypeNameTable[typen]+1 -end -end -return TypeNameTable -end -function UTILS.SaveStationaryListOfGroups(List,Path,Filename,Structured) -local filename=Filename or"StateListofGroups" -local data="--Save Stationary List of Groups: "..Filename.."\n" -for _,_group in pairs(List)do -local group=GROUP:FindByName(_group) -if group and group:IsAlive()then -local units=group:CountAliveUnits() -local position=group:GetVec3() -if Structured then -local structure=UTILS.GetCountPerTypeName(group) -local strucdata="" -for typen,anzahl in pairs(structure)do -strucdata=strucdata..typen.."=="..anzahl..";" -end -data=string.format("%s%s,%d,%d,%d,%d,%s\n",data,_group,units,position.x,position.y,position.z,strucdata) -else -data=string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z) -end -else -data=string.format("%s%s,0,0,0,0\n",data,_group) -end -end -local outcome=UTILS.SaveToFile(Path,Filename,data) -return outcome -end -function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured) -local filename=Filename or"SetOfGroups" -local data="--Save SET of groups: "..Filename.."\n" -local List=Set:GetSetObjects() -for _,_group in pairs(List)do -local group=_group -if group and group:IsAlive()then -local name=group:GetName() -local template=string.gsub(name,"-(.+)$","") -if string.find(template,"#")then -template=string.gsub(name,"#(%d+)$","") -end -local units=group:CountAliveUnits() -local position=group:GetVec3() -if Structured then -local structure=UTILS.GetCountPerTypeName(group) -local strucdata="" -for typen,anzahl in pairs(structure)do -strucdata=strucdata..typen.."=="..anzahl..";" -end -data=string.format("%s%s,%s,%d,%d,%d,%d,%s\n",data,name,template,units,position.x,position.y,position.z,strucdata) -else -data=string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z) -end -end -end -local outcome=UTILS.SaveToFile(Path,Filename,data) -return outcome -end -function UTILS.SaveSetOfStatics(Set,Path,Filename) -local filename=Filename or"SetOfStatics" -local data="--Save SET of statics: "..Filename.."\n" -local List=Set:GetSetObjects() -for _,_group in pairs(List)do -local group=_group -if group and group:IsAlive()then -local name=group:GetName() -local position=group:GetVec3() -data=string.format("%s%s,%d,%d,%d\n",data,name,position.x,position.y,position.z) -end -end -local outcome=UTILS.SaveToFile(Path,Filename,data) -return outcome -end -function UTILS.SaveStationaryListOfStatics(List,Path,Filename) -local filename=Filename or"StateListofStatics" -local data="--Save Stationary List of Statics: "..Filename.."\n" -for _,_group in pairs(List)do -local group=STATIC:FindByName(_group,false) -if group and group:IsAlive()then -local position=group:GetVec3() -data=string.format("%s%s,1,%d,%d,%d\n",data,_group,position.x,position.y,position.z) -else -data=string.format("%s%s,0,0,0,0\n",data,_group) -end -end -local outcome=UTILS.SaveToFile(Path,Filename,data) -return outcome -end -function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density) -local fires={} -local function Smokers(name,coord,effect,density) -local eff=math.random(8) -if type(effect)=="number"then eff=effect end -coord:BigSmokeAndFire(eff,density,name) -table.insert(fires,name) -end -local function Cruncher(group,typename,anzahl) -local units=group:GetUnits() -local reduced=0 -for _,_unit in pairs(units)do -local typo=_unit:GetTypeName() -if typename==typo then -if Cinematic then -local coordinate=_unit:GetCoordinate() -local name=_unit:GetName() -Smokers(name,coordinate,Effect,Density) -end -_unit:Destroy(false) -reduced=reduced+1 -if reduced==anzahl then break end -end -end -end -local reduce=true -if Reduce==false then reduce=false end -local filename=Filename or"StateListofGroups" -local datatable={} -if UTILS.CheckFileExists(Path,filename)then -local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename) -table.remove(loadeddata,1) -for _id,_entry in pairs(loadeddata)do -local dataset=UTILS.Split(_entry,",") -local groupname=dataset[1] -local size=tonumber(dataset[2]) -local posx=tonumber(dataset[3]) -local posy=tonumber(dataset[4]) -local posz=tonumber(dataset[5]) -local structure=dataset[6] -local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz}) -local data={groupname=groupname,size=size,coordinate=coordinate,group=GROUP:FindByName(groupname)} -if reduce then -local actualgroup=GROUP:FindByName(groupname) -if actualgroup and actualgroup:IsAlive()and actualgroup:CountAliveUnits()>size then -if Structured and structure then -local loadedstructure={} -local strcset=UTILS.Split(structure,";") -for _,_data in pairs(strcset)do -local datasplit=UTILS.Split(_data,"==") -loadedstructure[datasplit[1]]=tonumber(datasplit[2]) -end -local originalstructure=UTILS.GetCountPerTypeName(actualgroup) -for _name,_number in pairs(originalstructure)do -local loadednumber=0 -if loadedstructure[_name]then -loadednumber=loadedstructure[_name] -end -local reduce=false -if loadednumber<_number then reduce=true end -if reduce then -Cruncher(actualgroup,_name,_number-loadednumber) -end -end -else -local reduction=actualgroup:CountAliveUnits()-size -local units=actualgroup:GetUnits() -local units2=UTILS.ShuffleTable(units) -for i=1,reduction do -units2[i]:Destroy(false) -end -end -end -end -table.insert(datatable,data) -end -else -return nil -end -return datatable,fires -end -function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density) -local fires={} -local usedtemplates={} -local spawn=true -if Spawn==false then spawn=false end -local filename=Filename or"SetOfGroups" -local setdata=SET_GROUP:New() -local datatable={} -local function Smokers(name,coord,effect,density) -local eff=math.random(8) -if type(effect)=="number"then eff=effect end -coord:BigSmokeAndFire(eff,density,name) -table.insert(fires,name) -end -local function Cruncher(group,typename,anzahl) -local units=group:GetUnits() -local reduced=0 -for _,_unit in pairs(units)do -local typo=_unit:GetTypeName() -if typename==typo then -if Cinematic then -local coordinate=_unit:GetCoordinate() -local name=_unit:GetName() -Smokers(name,coordinate,Effect,Density) -end -_unit:Destroy(false) -reduced=reduced+1 -if reduced==anzahl then break end -end -end -end -local function PostSpawn(args) -local spwndgrp=args[1] -local size=args[2] -local structure=args[3] -setdata:AddObject(spwndgrp) -local actualsize=spwndgrp:CountAliveUnits() -if actualsize>size then -if Structured and structure then -local loadedstructure={} -local strcset=UTILS.Split(structure,";") -for _,_data in pairs(strcset)do -local datasplit=UTILS.Split(_data,"==") -loadedstructure[datasplit[1]]=tonumber(datasplit[2]) -end -local originalstructure=UTILS.GetCountPerTypeName(spwndgrp) -for _name,_number in pairs(originalstructure)do -local loadednumber=0 -if loadedstructure[_name]then -loadednumber=loadedstructure[_name] -end -local reduce=false -if loadednumber<_number then reduce=true end -if reduce then -Cruncher(spwndgrp,_name,_number-loadednumber) -end -end -else -local reduction=actualsize-size -local units=spwndgrp:GetUnits() -local units2=UTILS.ShuffleTable(units) -for i=1,reduction do -units2[i]:Destroy(false) -end -end -end -end -local function MultiUse(Data) -local template=Data.template -if template and usedtemplates[template]and usedtemplates[template].used and usedtemplates[template].used>1 then -if not usedtemplates[template].done then -local spwnd=0 -local spawngrp=SPAWN:New(template) -spawngrp:InitLimit(0,usedtemplates[template].used) -for _,_entry in pairs(usedtemplates[template].data)do -spwnd=spwnd+1 -local sgrp=spawngrp:SpawnFromCoordinate(_entry.coordinate,spwnd) -BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure}) -end -usedtemplates[template].done=true -end -return true -else -return false -end -end -if UTILS.CheckFileExists(Path,filename)then -local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename) -table.remove(loadeddata,1) -for _id,_entry in pairs(loadeddata)do -local dataset=UTILS.Split(_entry,",") -local groupname=dataset[1] -local template=dataset[2] -local size=tonumber(dataset[3]) -local posx=tonumber(dataset[4]) -local posy=tonumber(dataset[5]) -local posz=tonumber(dataset[6]) -local structure=dataset[7] -local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz}) -local group=nil -if size>0 then -local data={groupname=groupname,size=size,coordinate=coordinate,template=template,structure=structure} -table.insert(datatable,data) -if usedtemplates[template]then -usedtemplates[template].used=usedtemplates[template].used+1 -table.insert(usedtemplates[template].data,data) -else -usedtemplates[template]={ -data={}, -used=1, -done=false, -} -table.insert(usedtemplates[template].data,data) -end -end -end -for _id,_entry in pairs(datatable)do -if spawn and not MultiUse(_entry)and _entry.size>0 then -local group=SPAWN:New(_entry.template) -local sgrp=group:SpawnFromCoordinate(_entry.coordinate) -BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure}) -end -end -else -return nil -end -if spawn then -return setdata,fires -else -return datatable -end -end -function UTILS.LoadSetOfStatics(Path,Filename) -local filename=Filename or"SetOfStatics" -local datatable=SET_STATIC:New() -if UTILS.CheckFileExists(Path,filename)then -local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename) -table.remove(loadeddata,1) -for _id,_entry in pairs(loadeddata)do -local dataset=UTILS.Split(_entry,",") -local staticname=dataset[1] -local StaticObject=STATIC:FindByName(staticname,false) -if StaticObject then -datatable:AddObject(StaticObject) -end -end -else -return nil -end -return datatable -end -function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density) -local fires={} -local reduce=true -if Reduce==false then reduce=false end -local filename=Filename or"StateListofStatics" -local datatable={} -if UTILS.CheckFileExists(Path,filename)then -local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename) -table.remove(loadeddata,1) -for _id,_entry in pairs(loadeddata)do -local dataset=UTILS.Split(_entry,",") -local staticname=dataset[1] -local size=tonumber(dataset[2]) -local posx=tonumber(dataset[3]) -local posy=tonumber(dataset[4]) -local posz=tonumber(dataset[5]) -local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz}) -local data={staticname=staticname,size=size,coordinate=coordinate,static=STATIC:FindByName(staticname,false)} -table.insert(datatable,data) -if size==0 and reduce then -local static=STATIC:FindByName(staticname,false) -if static then -if Dead then -local deadobject=SPAWNSTATIC:NewFromStatic(staticname,static:GetCountry()) -deadobject:InitDead(true) -local heading=static:GetHeading() -local coord=static:GetCoordinate() -static:Destroy(false) -deadobject:SpawnFromCoordinate(coord,heading,staticname) -if Cinematic then -local effect=math.random(8) -if type(Effect)=="number"then -effect=Effect -end -coord:BigSmokeAndFire(effect,Density,staticname) -table.insert(fires,staticname) -end -else -static:Destroy(false) -end -end -end -end -else -return nil -end -return datatable,fires -end -function UTILS.BearingToCardinal(Heading) -if Heading>=0 and Heading<=22 then return"North" -elseif Heading>=23 and Heading<=66 then return"North-East" -elseif Heading>=67 and Heading<=101 then return"East" -elseif Heading>=102 and Heading<=146 then return"South-East" -elseif Heading>=147 and Heading<=201 then return"South" -elseif Heading>=202 and Heading<=246 then return"South-West" -elseif Heading>=247 and Heading<=291 then return"West" -elseif Heading>=292 and Heading<=338 then return"North-West" -elseif Heading>=339 then return"North" -end -end -function UTILS.ToStringBRAANATO(FromGrp,ToGrp) -local BRAANATO="Merged." -local GroupNumber=ToGrp:GetSize() -local GroupWords="Singleton" -if GroupNumber==2 then GroupWords="Two-Ship" -elseif GroupNumber>=3 then GroupWords="Heavy" -end -local grpLeadUnit=ToGrp:GetUnit(1) -local tgtCoord=grpLeadUnit:GetCoordinate() -local currentCoord=FromGrp:GetCoordinate() -local hdg=UTILS.Round(ToGrp:GetHeading()/100,1)*100 -local bearing=UTILS.Round(currentCoord:HeadingTo(tgtCoord),0) -local rangeMetres=tgtCoord:Get2DDistance(currentCoord) -local rangeNM=UTILS.Round(UTILS.MetersToNM(rangeMetres),0) -local aspect=tgtCoord:ToStringAspect(currentCoord) -local alt=UTILS.Round(UTILS.MetersToFeet(grpLeadUnit:GetAltitude())/1000,0) -local track=UTILS.BearingToCardinal(hdg) -if rangeNM>3 then -if aspect==""then -BRAANATO=string.format("%s, BRA, %03d, %d miles, Angels %d, Track %s",GroupWords,bearing,rangeNM,alt,track) -else -BRAANATO=string.format("%s, BRAA, %03d, %d miles, Angels %d, %s, Track %s",GroupWords,bearing,rangeNM,alt,aspect,track) -end -end -return BRAANATO -end -function UTILS.IsInTable(Table,Object,Key) -for key,object in pairs(Table)do -if Key then -if Object[Key]==object[Key]then -return true -end -else -if object==Object then -return true -end -end -end -return false -end -function UTILS.IsAnyInTable(Table,Objects,Key) -for _,Object in pairs(UTILS.EnsureTable(Objects))do -for key,object in pairs(Table)do -if Key then -if Object[Key]==object[Key]then -return true -end -else -if object==Object then -return true -end -end -end -end -return false -end -function UTILS.PlotRacetrack(Coordinate,Altitude,Speed,Heading,Leg,Coalition,Color,Alpha,LineType,ReadOnly) -local fix_coordinate=Coordinate -local altitude=Altitude -local speed=Speed or 350 -local heading=Heading or 270 -local leg_distance=Leg or 10 -local coalition=Coalition or-1 -local color=Color or{1,0,0} -local alpha=Alpha or 1 -local lineType=LineType or 1 -speed=UTILS.IasToTas(speed,UTILS.FeetToMeters(altitude),oatcorr) -local turn_radius=0.0211*speed-3.01 -local point_two=fix_coordinate:Translate(UTILS.NMToMeters(leg_distance),heading,true,false) -local point_three=point_two:Translate(UTILS.NMToMeters(turn_radius)*2,heading-90,true,false) -local point_four=fix_coordinate:Translate(UTILS.NMToMeters(turn_radius)*2,heading-90,true,false) -local circle_center_fix_four=point_two:Translate(UTILS.NMToMeters(turn_radius),heading-90,true,false) -local circle_center_two_three=fix_coordinate:Translate(UTILS.NMToMeters(turn_radius),heading-90,true,false) -fix_coordinate:LineToAll(point_two,coalition,color,alpha,lineType) -point_four:LineToAll(point_three,coalition,color,alpha,lineType) -circle_center_fix_four:CircleToAll(UTILS.NMToMeters(turn_radius),coalition,color,alpha,nil,0,lineType) -circle_center_two_three:CircleToAll(UTILS.NMToMeters(turn_radius),coalition,color,alpha,nil,0,lineType) -end -function UTILS.TimeNow() -return UTILS.SecondsToClock(timer.getAbsTime(),false,false) -end -function UTILS.TimeDifferenceInSeconds(start_time,end_time) -return UTILS.ClockToSeconds(end_time)-UTILS.ClockToSeconds(start_time) -end -function UTILS.TimeLaterThan(time_string) -if timer.getAbsTime()>UTILS.ClockToSeconds(time_string)then -return true -end -return false -end -function UTILS.TimeBefore(time_string) -if timer.getAbsTime()max then value=max end -return value -end -function UTILS.ClampAngle(value) -if value>360 then return value-360 end -if value<0 then return value+360 end -return value -end -function UTILS.RemapValue(value,old_min,old_max,new_min,new_max) -new_min=new_min or 0 -new_max=new_max or 100 -local old_range=old_max-old_min -local new_range=new_max-new_min -local percentage=(value-old_min)/old_range -return(new_range*percentage)+new_min -end -function UTILS.RandomPointInTriangle(pt1,pt2,pt3) -local pt={math.random(),math.random()} -table.sort(pt) -local s=pt[1] -local t=pt[2]-pt[1] -local u=1-pt[2] -return{x=s*pt1.x+t*pt2.x+u*pt3.x, -y=s*pt1.y+t*pt2.y+u*pt3.y} -end -function UTILS.AngleBetween(angle,min,max) -angle=(360+(angle%360))%360 -min=(360+min%360)%360 -max=(360+max%360)%360 -if min0 then -for _,property in pairs(zone["properties"])do -return_table[property["key"]]=property["value"] -end -return return_table -else -BASE:I(string.format("%s doesn't have any properties",zone_name)) -return{} -end -end -end -end -function UTILS.RotatePointAroundPivot(point,pivot,angle) -local radians=math.rad(angle) -local x=point.x-pivot.x -local y=point.y-pivot.y -local rotated_x=x*math.cos(radians)-y*math.sin(radians) -local rotatex_y=x*math.sin(radians)+y*math.cos(radians) -local original_x=rotated_x+pivot.x -local original_y=rotatex_y+pivot.y -return{x=original_x,y=original_y} -end -function UTILS.UniqueName(base) -base=base or"" -local ran=tostring(math.random(0,1000000)) -if base==""then -return ran -end -return base.."_"..ran -end -function string.startswith(str,value) -return string.sub(str,1,string.len(value))==value -end -function string.endswith(str,value) -return value==""or str:sub(-#value)==value -end -function string.split(input,separator) -local parts={} -for part in input:gmatch("[^"..separator.."]+")do -table.insert(parts,part) -end -return parts -end -function string.contains(str,value) -return string.match(str,value) -end -function table.contains(tbl,element) -if element==nil or tbl==nil then return false end -local index=1 -while tbl[index]do -if tbl[index]==element then -return true -end -index=index+1 -end -return false -end -function table.contains_key(tbl,key) -if tbl[key]~=nil then return true else return false end -end -function table.insert_unique(tbl,element) -if element==nil or tbl==nil then return end -if not table.contains(tbl,element)then -table.insert(tbl,element) -end -end -function table.remove_by_value(tbl,element) -local indices_to_remove={} -local index=1 -for _,value in pairs(tbl)do -if value==element then -table.insert(indices_to_remove,index) -end -index=index+1 -end -for _,idx in pairs(indices_to_remove)do -table.remove(tbl,idx) -end -end -function table.remove_key(table,key) -local element=table[key] -table[key]=nil -return element -end -function table.index_of(table,element) -for i,v in ipairs(table)do -if v==element then -return i -end -end -return nil -end -function table.length(T) -local count=0 -for _ in pairs(T)do count=count+1 end -return count -end -function table.slice(tbl,first,last) -local sliced={} -local start=first or 1 -local stop=last or table.length(tbl) -local count=1 -for key,value in pairs(tbl)do -if count>=start and count<=stop then -sliced[key]=value -end -count=count+1 -end -return sliced -end -function table.count_value(tbl,value) -local count=0 -for _,item in pairs(tbl)do -if item==value then count=count+1 end -end -return count -end -function table.combine(t1,t2) -if t1==nil and t2==nil then -BASE:E("Both tables were empty!") -end -if t1==nil then return t2 end -if t2==nil then return t1 end -for i=1,#t2 do -t1[#t1+1]=t2[i] -end -return t1 -end -function table.merge(t1,t2) -for k,v in pairs(t2)do -if(type(v)=="table")and(type(t1[k]or false)=="table")then -table.merge(t1[k],t2[k]) -else -t1[k]=v -end -end -return t1 -end -function table.add(tbl,item) -tbl[#tbl+1]=item -end -function table.shuffle(tbl) -local new_table={} -for _,value in ipairs(tbl)do -local pos=math.random(1,#new_table+1) -table.insert(new_table,pos,value) -end -return new_table -end -function table.find_key_value_pair(tbl,key,value) -for k,v in pairs(tbl)do -if type(v)=="table"then -local result=table.find_key_value_pair(v,key,value) -if result~=nil then -return result -end -elseif k==key and v==value then -return tbl -end -end -return nil -end -function UTILS.DecimalToOctal(Number) -if Number<8 then return Number end -local number=tonumber(Number) -local octal="" -local n=1 -while number>7 do -local number1=number%8 -octal=string.format("%d",number1)..octal -local number2=math.abs(number/8) -if number2<8 then -octal=string.format("%d",number2)..octal -end -number=number2 -n=n+1 -end -return tonumber(octal) -end -function UTILS.OctalToDecimal(Number) -return tonumber(Number,8) -end -PROFILER={ -ClassName="PROFILER", -Counters={}, -dInfo={}, -fTime={}, -fTimeTotal={}, -eventHandler={}, -logUnknown=false, -ThreshCPS=0.0, -ThreshTtot=0.005, -fileNamePrefix="MooseProfiler", -fileNameSuffix="txt" -} -function PROFILER.Start(Delay,Duration) -local go=true -if not os then -env.error("ERROR: Profiler needs os to be de-sanitized!") -go=false -end -if not io then -env.error("ERROR: Profiler needs io to be desanitized!") -go=false -end -if not lfs then -env.error("ERROR: Profiler needs lfs to be desanitized!") -go=false -end -if not go then -return -end -if Delay and Delay>0 then -BASE:ScheduleOnce(Delay,PROFILER.Start,0,Duration) -else -PROFILER.TstartGame=timer.getTime() -PROFILER.TstartOS=os.clock() -world.addEventHandler(PROFILER.eventHandler) -env.info('############################ Profiler Started ############################') -if Duration then -env.info(string.format("- Will be running for %d seconds",Duration)) -else -env.info(string.format("- Will be stopped when mission ends")) -end -env.info(string.format("- Calls per second threshold %.3f/sec",PROFILER.ThreshCPS)) -env.info(string.format("- Total function time threshold %.3f sec",PROFILER.ThreshTtot)) -env.info(string.format("- Output file \"%s\" in your DCS log file folder",PROFILER.getfilename(PROFILER.fileNameSuffix))) -env.info(string.format("- Output file \"%s\" in CSV format",PROFILER.getfilename("csv"))) -env.info('###############################################################################') -local duration=Duration or 600 -trigger.action.outText("### Profiler running ###",duration) -debug.sethook(PROFILER.hook,"cr") -if Duration then -PROFILER.Stop(Duration) -end -end -end -function PROFILER.Stop(Delay) -if Delay and Delay>0 then -BASE:ScheduleOnce(Delay,PROFILER.Stop) -end -end -function PROFILER.Stop(Delay) -if Delay and Delay>0 then -BASE:ScheduleOnce(Delay,PROFILER.Stop) -else -debug.sethook() -local runTimeGame=timer.getTime()-PROFILER.TstartGame -local runTimeOS=os.clock()-PROFILER.TstartOS -PROFILER.showInfo(runTimeGame,runTimeOS) -end -end -function PROFILER.eventHandler:onEvent(event) -if event.id==world.event.S_EVENT_MISSION_END then -PROFILER.Stop() -end -end -function PROFILER.hook(event) -local f=debug.getinfo(2,"f").func -if event=='call'then -if PROFILER.Counters[f]==nil then -PROFILER.Counters[f]=1 -PROFILER.dInfo[f]=debug.getinfo(2,"Sn") -if PROFILER.fTimeTotal[f]==nil then -PROFILER.fTimeTotal[f]=0 -end -else -PROFILER.Counters[f]=PROFILER.Counters[f]+1 -end -if PROFILER.fTime[f]==nil then -PROFILER.fTime[f]=os.clock() -end -elseif(event=='return')then -if PROFILER.fTime[f]~=nil then -PROFILER.fTimeTotal[f]=PROFILER.fTimeTotal[f]+(os.clock()-PROFILER.fTime[f]) -PROFILER.fTime[f]=nil -end -end -end -function PROFILER.getData(func) -local n=PROFILER.dInfo[func] -if n.what=="C"then -return n.name,"?","?",PROFILER.fTimeTotal[func] -end -return n.name,n.short_src,n.linedefined,PROFILER.fTimeTotal[func] -end -function PROFILER._flog(f,txt) -f:write(txt.."\r\n") -end -function PROFILER.showTable(data,f,runTimeGame) -for i=1,#data do -local t=data[i] -local cps=t.count/runTimeGame -local threshCPS=cps>=PROFILER.ThreshCPS -local threshTot=t.tm>=PROFILER.ThreshTtot -if threshCPS and threshTot then -local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call %s line %s",t.func,t.count,cps,t.tm,t.tm/runTimeGame*100,t.tm/t.count,tostring(t.src),tostring(t.line)) -PROFILER._flog(f,text) -end -end -end -function PROFILER.printCSV(data,runTimeGame) -local file=PROFILER.getfilename("csv") -local g=io.open(file,'w') -local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number," -g:write(text.."\r\n") -for i=1,#data do -local t=data[i] -local cps=t.count/runTimeGame -local txt=string.format("%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,",t.func,t.count,cps,t.tm,t.tm/runTimeGame*100,t.tm/t.count,tostring(t.src),tostring(t.line)) -g:write(txt.."\r\n") -end -g:close() -end -function PROFILER.getfilename(ext) -local dir=lfs.writedir()..[[Logs\]] -ext=ext or PROFILER.fileNameSuffix -local file=dir..PROFILER.fileNamePrefix.."."..ext -if not UTILS.FileExists(file)then -return file -end -for i=1,999 do -local file=string.format("%s%s-%03d.%s",dir,PROFILER.fileNamePrefix,i,ext) -if not UTILS.FileExists(file)then -return file -end -end -end -function PROFILER.showInfo(runTimeGame,runTimeOS) -local file=PROFILER.getfilename(PROFILER.fileNameSuffix) -local f=io.open(file,'w') -local Ttot=0 -local Calls=0 -local t={} -local tcopy=nil -local tserialize=nil -local tforgen=nil -local tpairs=nil -for func,count in pairs(PROFILER.Counters)do -local s,src,line,tm=PROFILER.getData(func) -if PROFILER.logUnknown==true then -if s==nil then s=""end -end -if s~=nil then -local T= -{func=s, -src=src, -line=line, -count=count, -tm=tm, -} -if s=="_copy"then -if tcopy==nil then -tcopy=T -else -tcopy.count=tcopy.count+T.count -tcopy.tm=tcopy.tm+T.tm -end -elseif s=="_Serialize"then -if tserialize==nil then -tserialize=T -else -tserialize.count=tserialize.count+T.count -tserialize.tm=tserialize.tm+T.tm -end -elseif s=="(for generator)"then -if tforgen==nil then -tforgen=T -else -tforgen.count=tforgen.count+T.count -tforgen.tm=tforgen.tm+T.tm -end -elseif s=="pairs"then -if tpairs==nil then -tpairs=T -else -tpairs.count=tpairs.count+T.count -tpairs.tm=tpairs.tm+T.tm -end -else -table.insert(t,T) -end -Ttot=Ttot+tm -Calls=Calls+count -end -end -if tcopy then -table.insert(t,tcopy) -end -if tserialize then -table.insert(t,tserialize) -end -if tforgen then -table.insert(t,tforgen) -end -if tpairs then -table.insert(t,tpairs) -end -env.info('############################ Profiler Stopped ############################') -env.info(string.format("* Runtime Game : %s = %d sec",UTILS.SecondsToClock(runTimeGame,true),runTimeGame)) -env.info(string.format("* Runtime Real : %s = %d sec",UTILS.SecondsToClock(runTimeOS,true),runTimeOS)) -env.info(string.format("* Function time : %s = %.1f sec (%.1f percent of runtime game)",UTILS.SecondsToClock(Ttot,true),Ttot,Ttot/runTimeGame*100)) -env.info(string.format("* Total functions : %d",#t)) -env.info(string.format("* Total func calls : %d",Calls)) -env.info(string.format("* Writing to file : \"%s\"",file)) -env.info(string.format("* Writing to file : \"%s\"",PROFILER.getfilename("csv"))) -env.info("##############################################################################") -table.sort(t,function(a,b)return a.tm>b.tm end) -PROFILER._flog(f,"") -PROFILER._flog(f,"************************************************************************************************************************") -PROFILER._flog(f,"************************************************************************************************************************") -PROFILER._flog(f,"************************************************************************************************************************") -PROFILER._flog(f,"") -PROFILER._flog(f,"-------------------------") -PROFILER._flog(f,"---- Profiler Report ----") -PROFILER._flog(f,"-------------------------") -PROFILER._flog(f,"") -PROFILER._flog(f,string.format("* Runtime Game : %s = %.1f sec",UTILS.SecondsToClock(runTimeGame,true),runTimeGame)) -PROFILER._flog(f,string.format("* Runtime Real : %s = %.1f sec",UTILS.SecondsToClock(runTimeOS,true),runTimeOS)) -PROFILER._flog(f,string.format("* Function time : %s = %.1f sec (%.1f %% of runtime game)",UTILS.SecondsToClock(Ttot,true),Ttot,Ttot/runTimeGame*100)) -PROFILER._flog(f,"") -PROFILER._flog(f,string.format("* Total functions = %d",#t)) -PROFILER._flog(f,string.format("* Total func calls = %d",Calls)) -PROFILER._flog(f,"") -PROFILER._flog(f,string.format("* Calls per second threshold = %.3f/sec",PROFILER.ThreshCPS)) -PROFILER._flog(f,string.format("* Total func time threshold = %.3f sec",PROFILER.ThreshTtot)) -PROFILER._flog(f,"") -PROFILER._flog(f,"************************************************************************************************************************") -PROFILER._flog(f,"") -PROFILER.showTable(t,f,runTimeGame) -table.sort(t,function(a,b)return a.tm/a.count>b.tm/b.count end) -PROFILER._flog(f,"") -PROFILER._flog(f,"************************************************************************************************************************") -PROFILER._flog(f,"") -PROFILER._flog(f,"--------------------------------------") -PROFILER._flog(f,"---- Data Sorted by Time per Call ----") -PROFILER._flog(f,"--------------------------------------") -PROFILER._flog(f,"") -PROFILER.showTable(t,f,runTimeGame) -table.sort(t,function(a,b)return a.count>b.count end) -PROFILER._flog(f,"") -PROFILER._flog(f,"************************************************************************************************************************") -PROFILER._flog(f,"") -PROFILER._flog(f,"------------------------------------") -PROFILER._flog(f,"---- Data Sorted by Total Calls ----") -PROFILER._flog(f,"------------------------------------") -PROFILER._flog(f,"") -PROFILER.showTable(t,f,runTimeGame) -PROFILER._flog(f,"") -PROFILER._flog(f,"************************************************************************************************************************") -PROFILER._flog(f,"************************************************************************************************************************") -PROFILER._flog(f,"************************************************************************************************************************") -f:close() -PROFILER.printCSV(t,runTimeGame) -end -TEMPLATE={ -ClassName="TEMPLATE", -Ground={}, -Naval={}, -Airplane={}, -Helicopter={}, -} -TEMPLATE.TypeGround={ -InfantryAK="Infantry AK", -ParatrooperAKS74="Paratrooper AKS-74", -ParatrooperRPG16="Paratrooper RPG-16", -SoldierWWIIUS="soldier_wwii_us", -InfantryM248="Infantry M249", -SoldierM4="Soldier M4", -} -TEMPLATE.TypeNaval={ -Ticonderoga="TICONDEROG", -} -TEMPLATE.TypeAirplane={ -A10C="A-10C", -} -TEMPLATE.TypeHelicopter={ -AH1W="AH-1W", -} -function TEMPLATE.GetGround(TypeName,GroupName,CountryID,Vec3,Nunits,Radius) -TypeName=TypeName or TEMPLATE.TypeGround.SoldierM4 -GroupName=GroupName or"Ground-1" -CountryID=CountryID or country.id.USA -Vec3=Vec3 or{x=0,y=0,z=0} -Nunits=Nunits or 1 -Radius=Radius or 50 -local template=UTILS.DeepCopy(TEMPLATE.GenericGround) -template.name=GroupName -template.CountryID=CountryID -template.CoalitionID=coalition.getCountryCoalition(template.CountryID) -template.CategoryID=Unit.Category.GROUND_UNIT -template.units[1].type=TypeName -template.units[1].name=GroupName.."-1" -if Vec3 then -TEMPLATE.SetPositionFromVec3(template,Vec3) -end -TEMPLATE.SetUnits(template,Nunits,COORDINATE:NewFromVec3(Vec3),Radius) -return template -end -function TEMPLATE.GetNaval(TypeName,GroupName,CountryID,Vec3,Nunits,Radius) -TypeName=TypeName or TEMPLATE.TypeNaval.Ticonderoga -GroupName=GroupName or"Naval-1" -CountryID=CountryID or country.id.USA -Vec3=Vec3 or{x=0,y=0,z=0} -Nunits=Nunits or 1 -Radius=Radius or 500 -local template=UTILS.DeepCopy(TEMPLATE.GenericNaval) -template.name=GroupName -template.CountryID=CountryID -template.CoalitionID=coalition.getCountryCoalition(template.CountryID) -template.CategoryID=Unit.Category.SHIP -template.units[1].type=TypeName -template.units[1].name=GroupName.."-1" -if Vec3 then -TEMPLATE.SetPositionFromVec3(template,Vec3) -end -TEMPLATE.SetUnits(template,Nunits,COORDINATE:NewFromVec3(Vec3),Radius) -return template -end -function TEMPLATE.GetAirplane(TypeName,GroupName,CountryID,Vec3,Nunits,Radius) -TypeName=TypeName or TEMPLATE.TypeAirplane.A10C -GroupName=GroupName or"Airplane-1" -CountryID=CountryID or country.id.USA -Vec3=Vec3 or{x=0,y=1000,z=0} -Nunits=Nunits or 1 -Radius=Radius or 100 -local template=TEMPLATE._GetAircraft(true,TypeName,GroupName,CountryID,Vec3,Nunits,Radius) -return template -end -function TEMPLATE.GetHelicopter(TypeName,GroupName,CountryID,Vec3,Nunits,Radius) -TypeName=TypeName or TEMPLATE.TypeHelicopter.AH1W -GroupName=GroupName or"Helicopter-1" -CountryID=CountryID or country.id.USA -Vec3=Vec3 or{x=0,y=500,z=0} -Nunits=Nunits or 1 -Radius=Radius or 100 -Nunits=math.min(Nunits,4) -local template=TEMPLATE._GetAircraft(false,TypeName,GroupName,CountryID,Vec3,Nunits,Radius) -return template -end -function TEMPLATE._GetAircraft(Airplane,TypeName,GroupName,CountryID,Vec3,Nunits,Radius) -TypeName=TypeName -GroupName=GroupName or"Aircraft-1" -CountryID=CountryID or country.id.USA -Vec3=Vec3 or{x=0,y=0,z=0} -Nunits=Nunits or 1 -Radius=Radius or 100 -local template=UTILS.DeepCopy(TEMPLATE.GenericAircraft) -template.name=GroupName -template.CountryID=CountryID -template.CoalitionID=coalition.getCountryCoalition(template.CountryID) -if Airplane then -template.CategoryID=Unit.Category.AIRPLANE -else -template.CategoryID=Unit.Category.HELICOPTER -end -template.units[1].type=TypeName -template.units[1].name=GroupName.."-1" -if Vec3 then -TEMPLATE.SetPositionFromVec3(template,Vec3) -end -TEMPLATE.SetUnits(template,Nunits,COORDINATE:NewFromVec3(Vec3),Radius) -return template -end -function TEMPLATE.SetPositionFromVec2(Template,Vec2) -Template.x=Vec2.x -Template.y=Vec2.y -for _,unit in pairs(Template.units)do -unit.x=Vec2.x -unit.y=Vec2.y -end -Template.route.points[1].x=Vec2.x -Template.route.points[1].y=Vec2.y -Template.route.points[1].alt=0 -end -function TEMPLATE.SetPositionFromVec3(Template,Vec3) -local Vec2={x=Vec3.x,y=Vec3.z} -TEMPLATE.SetPositionFromVec2(Template,Vec2) -end -function TEMPLATE.SetUnits(Template,N,Coordinate,Radius) -local units=Template.units -local unit1=units[1] -local Vec3=Coordinate:GetVec3() -unit1.x=Vec3.x -unit1.y=Vec3.z -unit1.alt=Vec3.y -for i=2,N do -units[i]=UTILS.DeepCopy(unit1) -end -for i=1,N do -local unit=units[i] -unit.name=string.format("%s-%d",Template.name,i) -if i>1 then -local vec2=Coordinate:GetRandomCoordinateInRadius(Radius,5):GetVec2() -unit.x=vec2.x -unit.y=vec2.y -unit.alt=unit1.alt -end -end -end -function TEMPLATE.SetAirbase(Template,AirBase,ParkingSpots,EngineOn) -local AirbaseID=AirBase:GetID() -local point=Template.route.points[1] -if AirBase:IsAirdrome()then -point.airdromeId=AirbaseID -else -point.helipadId=AirbaseID -point.linkUnit=AirbaseID -end -if EngineOn then -point.action=COORDINATE.WaypointAction.FromParkingAreaHot -point.type=COORDINATE.WaypointType.TakeOffParkingHot -else -point.action=COORDINATE.WaypointAction.FromParkingArea -point.type=COORDINATE.WaypointType.TakeOffParking -end -for i,unit in ipairs(Template.units)do -unit.parking_id=ParkingSpots[i] -end -end -function TEMPLATE.AddWaypoint(Template,Waypoint) -table.insert(Template.route.points,Waypoint) -end -TEMPLATE.GenericGround= -{ -["visible"]=false, -["tasks"]={}, -["uncontrollable"]=false, -["task"]="Ground Nothing", -["route"]= -{ -["spans"]={}, -["points"]= -{ -[1]= -{ -["alt"]=0, -["type"]="Turning Point", -["ETA"]=0, -["alt_type"]="BARO", -["formation_template"]="", -["y"]=0, -["x"]=0, -["ETA_locked"]=true, -["speed"]=0, -["action"]="Off Road", -["task"]= -{ -["id"]="ComboTask", -["params"]= -{ -["tasks"]= -{ -}, -}, -}, -["speed_locked"]=true, -}, -}, -}, -["groupId"]=nil, -["hidden"]=false, -["units"]= -{ -[1]= -{ -["transportable"]= -{ -["randomTransportable"]=false, -}, -["skill"]="Average", -["type"]="Infantry AK", -["unitId"]=nil, -["y"]=0, -["x"]=0, -["name"]="Infantry AK-47 Rus", -["heading"]=0, -["playerCanDrive"]=false, -}, -}, -["y"]=0, -["x"]=0, -["name"]="Infantry AK-47 Rus", -["start_time"]=0, -} -TEMPLATE.GenericNaval= -{ -["visible"]=false, -["tasks"]={}, -["uncontrollable"]=false, -["route"]= -{ -["points"]= -{ -[1]= -{ -["alt"]=0, -["type"]="Turning Point", -["ETA"]=0, -["alt_type"]="BARO", -["formation_template"]="", -["y"]=0, -["x"]=0, -["ETA_locked"]=true, -["speed"]=0, -["action"]="Turning Point", -["task"]= -{ -["id"]="ComboTask", -["params"]= -{ -["tasks"]= -{ -}, -}, -}, -["speed_locked"]=true, -}, -}, -}, -["groupId"]=nil, -["hidden"]=false, -["units"]= -{ -[1]= -{ -["transportable"]= -{ -["randomTransportable"]=false, -}, -["skill"]="Average", -["type"]="TICONDEROG", -["unitId"]=nil, -["y"]=0, -["x"]=0, -["name"]="Naval-1-1", -["heading"]=0, -["modulation"]=0, -["frequency"]=127500000, -}, -}, -["y"]=0, -["x"]=0, -["name"]="Naval-1", -["start_time"]=0, -} -TEMPLATE.GenericAircraft= -{ -["groupId"]=nil, -["name"]="Rotary-1", -["uncontrolled"]=false, -["hidden"]=false, -["task"]="Nothing", -["y"]=0, -["x"]=0, -["start_time"]=0, -["communication"]=true, -["radioSet"]=false, -["frequency"]=127.5, -["modulation"]=0, -["taskSelected"]=true, -["tasks"]={}, -["route"]= -{ -["points"]= -{ -[1]= -{ -["y"]=0, -["x"]=0, -["alt"]=1000, -["alt_type"]="BARO", -["action"]="Turning Point", -["type"]="Turning Point", -["airdromeId"]=nil, -["task"]= -{ -["id"]="ComboTask", -["params"]= -{ -["tasks"]={}, -}, -}, -["ETA"]=0, -["ETA_locked"]=true, -["speed"]=100, -["speed_locked"]=true, -["formation_template"]="", -}, -}, -}, -["units"]= -{ -[1]= -{ -["name"]="Rotary-1-1", -["unitId"]=nil, -["type"]="AH-1W", -["onboard_num"]="050", -["livery_id"]="USA X Black", -["skill"]="High", -["ropeLength"]=15, -["speed"]=0, -["x"]=0, -["y"]=0, -["alt"]=10, -["alt_type"]="BARO", -["heading"]=0, -["psi"]=0, -["parking"]=nil, -["parking_id"]=nil, -["payload"]= -{ -["pylons"]={}, -["fuel"]="1250.0", -["flare"]=30, -["chaff"]=30, -["gun"]=100, -}, -["callsign"]= -{ -[1]=2, -[2]=1, -[3]=1, -["name"]="Springfield11", -}, -}, -}, -} -STTS={ -ClassName="STTS", -DIRECTORY="", -SRS_PORT=5002, -GOOGLE_CREDENTIALS="C:\\Users\\Ciaran\\Downloads\\googletts.json", -EXECUTABLE="DCS-SR-ExternalAudio.exe" -} -STTS.DIRECTORY="D:/DCS/_SRS" -STTS.SRS_PORT=5002 -STTS.GOOGLE_CREDENTIALS="C:\\Users\\Ciaran\\Downloads\\googletts.json" -STTS.EXECUTABLE="DCS-SR-ExternalAudio.exe" -function STTS.uuid() -local random=math.random -local template='yxxx-xxxxxxxxxxxx' -return string.gsub(template,'[xy]',function(c) -local v=(c=='x')and random(0,0xf)or random(8,0xb) -return string.format('%x',v) -end) -end -function STTS.round(x,n) -n=math.pow(10,n or 0) -x=x*n -if x>=0 then -x=math.floor(x+0.5) -else -x=math.ceil(x-0.5) -end -return x/n -end -function STTS.getSpeechTime(length,speed,isGoogle) -local maxRateRatio=3 -speed=speed or 1.0 -isGoogle=isGoogle or false -local speedFactor=1.0 -if isGoogle then -speedFactor=speed -else -if speed~=0 then -speedFactor=math.abs(speed)*(maxRateRatio-1)/10+1 -end -if speed<0 then -speedFactor=1/speedFactor -end -end -local wpm=math.ceil(100*speedFactor) -local cps=math.floor((wpm*5)/60) -if type(length)=="string"then -length=string.len(length) -end -return length/cps -end -function STTS.TextToSpeech(message,freqs,modulations,volume,name,coalition,point,speed,gender,culture,voice,googleTTS) -if os==nil or io==nil then -env.info("[DCS-STTS] LUA modules os or io are sanitized. skipping. ") -return -end -speed=speed or 1 -gender=gender or"female" -culture=culture or"" -voice=voice or"" -coalition=coalition or"0" -name=name or"ROBOT" -volume=1 -speed=1 -message=message:gsub("\"","\\\"") -local cmd=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h",STTS.DIRECTORY,STTS.EXECUTABLE,freqs or"305",modulations or"AM",coalition,STTS.SRS_PORT,name) -if voice~=""then -cmd=cmd..string.format(" -V \"%s\"",voice) -else -if culture~=""then -cmd=cmd..string.format(" -l %s",culture) -end -if gender~=""then -cmd=cmd..string.format(" -g %s",gender) -end -end -if googleTTS==true then -cmd=cmd..string.format(" -G \"%s\"",STTS.GOOGLE_CREDENTIALS) -end -if speed~=1 then -cmd=cmd..string.format(" -s %s",speed) -end -if volume~=1.0 then -cmd=cmd..string.format(" -v %s",volume) -end -if point and type(point)=="table"and point.x then -local lat,lon,alt=coord.LOtoLL(point) -lat=STTS.round(lat,4) -lon=STTS.round(lon,4) -alt=math.floor(alt) -cmd=cmd..string.format(" -L %s -O %s -A %s",lat,lon,alt) -end -cmd=cmd..string.format(" -t \"%s\"",message) -if string.len(cmd)>255 then -local filename=os.getenv('TMP').."\\DCS_STTS-"..STTS.uuid()..".bat" -local script=io.open(filename,"w+") -script:write(cmd.." && exit") -script:close() -cmd=string.format("\"%s\"",filename) -timer.scheduleFunction(os.remove,filename,timer.getTime()+1) -end -if string.len(cmd)>255 then -env.info("[DCS-STTS] - cmd string too long") -env.info("[DCS-STTS] TextToSpeech Command :\n"..cmd.."\n") -end -os.execute(cmd) -return STTS.getSpeechTime(message,speed,googleTTS) -end -function STTS.PlayMP3(pathToMP3,freqs,modulations,volume,name,coalition,point) -local cmd=string.format("start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h",STTS.DIRECTORY,STTS.EXECUTABLE,pathToMP3,freqs or"305",modulations or"AM",coalition or"0",STTS.SRS_PORT,name or"ROBOT",volume or"1") -if point and type(point)=="table"and point.x then -local lat,lon,alt=coord.LOtoLL(point) -lat=STTS.round(lat,4) -lon=STTS.round(lon,4) -alt=math.floor(alt) -cmd=cmd..string.format(" -L %s -O %s -A %s",lat,lon,alt) -end -env.info("[DCS-STTS] MP3/OGG Command :\n"..cmd.."\n") -os.execute(cmd) -end -do -FIFO={ -ClassName="FIFO", -lid="", -version="0.0.5", -counter=0, -pointer=0, -stackbypointer={}, -stackbyid={} -} -function FIFO:New() -local self=BASE:Inherit(self,BASE:New()) -self.pointer=0 -self.counter=0 -self.stackbypointer={} -self.stackbyid={} -self.uniquecounter=0 -self.lid=string.format("%s (%s) | ","FiFo",self.version) -self:T(self.lid.."Created.") -return self -end -function FIFO:Clear() -self:T(self.lid.."Clear") -self.pointer=0 -self.counter=0 -self.stackbypointer=nil -self.stackbyid=nil -self.stackbypointer={} -self.stackbyid={} -self.uniquecounter=0 -return self -end -function FIFO:Push(Object,UniqueID) -self:T(self.lid.."Push") -self:T({Object,UniqueID}) -self.pointer=self.pointer+1 -self.counter=self.counter+1 -local uniID=UniqueID -if not UniqueID then -self.uniquecounter=self.uniquecounter+1 -uniID=self.uniquecounter -end -self.stackbyid[uniID]={pointer=self.pointer,data=Object,uniqueID=uniID} -self.stackbypointer[self.pointer]={pointer=self.pointer,data=Object,uniqueID=uniID} -return self -end -function FIFO:Pull() -self:T(self.lid.."Pull") -if self.counter==0 then return nil end -local object=self.stackbypointer[1].data -self.stackbypointer[1]=nil -self.counter=self.counter-1 -self:Flatten() -return object -end -function FIFO:PullByPointer(Pointer) -self:T(self.lid.."PullByPointer "..tostring(Pointer)) -if self.counter==0 then return nil end -local object=self.stackbypointer[Pointer] -self.stackbypointer[Pointer]=nil -if object then self.stackbyid[object.uniqueID]=nil end -self.counter=self.counter-1 -self:Flatten() -if object then -return object.data -else -return nil -end -end -function FIFO:ReadByPointer(Pointer) -self:T(self.lid.."ReadByPointer "..tostring(Pointer)) -if self.counter==0 or not Pointer or not self.stackbypointer[Pointer]then return nil end -local object=self.stackbypointer[Pointer] -if object then -return object.data -else -return nil -end -end -function FIFO:ReadByID(UniqueID) -self:T(self.lid.."ReadByID "..tostring(UniqueID)) -if self.counter==0 or not UniqueID or not self.stackbyid[UniqueID]then return nil end -local object=self.stackbyid[UniqueID] -if object then -return object.data -else -return nil -end -end -function FIFO:PullByID(UniqueID) -self:T(self.lid.."PullByID "..tostring(UniqueID)) -if self.counter==0 then return nil end -local object=self.stackbyid[UniqueID] -if object then -return self:PullByPointer(object.pointer) -else -return nil -end -end -function FIFO:Flatten() -self:T(self.lid.."Flatten") -local pointerstack={} -local idstack={} -local counter=0 -for _ID,_entry in pairs(self.stackbypointer)do -counter=counter+1 -pointerstack[counter]={pointer=counter,data=_entry.data,uniqueID=_entry.uniqueID} -end -for _ID,_entry in pairs(pointerstack)do -idstack[_entry.uniqueID]={pointer=_entry.pointer,data=_entry.data,uniqueID=_entry.uniqueID} -end -self.stackbypointer=nil -self.stackbypointer=pointerstack -self.stackbyid=nil -self.stackbyid=idstack -self.counter=counter -self.pointer=counter -return self -end -function FIFO:IsEmpty() -self:T(self.lid.."IsEmpty") -return self.counter==0 and true or false -end -function FIFO:GetSize() -self:T(self.lid.."GetSize") -return self.counter -end -function FIFO:Count() -self:T(self.lid.."Count") -return self.counter -end -function FIFO:IsNotEmpty() -self:T(self.lid.."IsNotEmpty") -return not self:IsEmpty() -end -function FIFO:GetPointerStack() -self:T(self.lid.."GetPointerStack") -return self.stackbypointer -end -function FIFO:HasUniqueID(UniqueID) -self:T(self.lid.."HasUniqueID") -if self.stackbyid[UniqueID]~=nil then -return true -else -return false -end -end -function FIFO:GetIDStack() -self:T(self.lid.."GetIDStack") -return self.stackbyid -end -function FIFO:GetIDStackSorted() -self:T(self.lid.."GetIDStackSorted") -local stack=self:GetIDStack() -local idstack={} -for _id,_entry in pairs(stack)do -idstack[#idstack+1]=_id -self:T({"pre",_id}) -end -local function sortID(a,b) -return a=1 then -self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom) -end -end -end -function BASE:F2(Arguments) -if BASE.Debug and _TraceOnOff then -local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") -local DebugInfoFrom=BASE.Debug.getinfo(3,"l") -if _TraceLevel>=2 then -self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom) -end -end -end -function BASE:F3(Arguments) -if BASE.Debug and _TraceOnOff then -local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") -local DebugInfoFrom=BASE.Debug.getinfo(3,"l") -if _TraceLevel>=3 then -self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom) -end -end -end -function BASE:_T(Arguments,DebugInfoCurrentParam,DebugInfoFromParam) -if BASE.Debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then -local DebugInfoCurrent=DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo(2,"nl") -local DebugInfoFrom=DebugInfoFromParam and DebugInfoFromParam or BASE.Debug.getinfo(3,"l") -local Function="function" -if DebugInfoCurrent.name then -Function=DebugInfoCurrent.name -end -if _TraceAll==true or _TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName].Method[Function]then -local LineCurrent=0 -if DebugInfoCurrent.currentline then -LineCurrent=DebugInfoCurrent.currentline -end -local LineFrom=0 -if DebugInfoFrom then -LineFrom=DebugInfoFrom.currentline -end -env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s",LineCurrent,LineFrom,"T",self.ClassName,self.ClassID,UTILS.BasicSerialize(Arguments))) -end -end -end -function BASE:T(Arguments) -if BASE.Debug and _TraceOnOff then -local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") -local DebugInfoFrom=BASE.Debug.getinfo(3,"l") -if _TraceLevel>=1 then -self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom) -end -end -end -function BASE:T2(Arguments) -if BASE.Debug and _TraceOnOff then -local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") -local DebugInfoFrom=BASE.Debug.getinfo(3,"l") -if _TraceLevel>=2 then -self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom) -end -end -end -function BASE:T3(Arguments) -if BASE.Debug and _TraceOnOff then -local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") -local DebugInfoFrom=BASE.Debug.getinfo(3,"l") -if _TraceLevel>=3 then -self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom) -end -end -end -function BASE:E(Arguments) -if BASE.Debug then -local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") -local DebugInfoFrom=BASE.Debug.getinfo(3,"l") -local Function="function" -if DebugInfoCurrent.name then -Function=DebugInfoCurrent.name -end -local LineCurrent=DebugInfoCurrent.currentline -local LineFrom=-1 -if DebugInfoFrom then -LineFrom=DebugInfoFrom.currentline -end -env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s(%s)",LineCurrent,LineFrom,"E",self.ClassName,self.ClassID,Function,UTILS.BasicSerialize(Arguments))) -else -env.info(string.format("%1s:%30s%05d(%s)","E",self.ClassName,self.ClassID,UTILS.BasicSerialize(Arguments))) -end -end -function BASE:I(Arguments) -if BASE.Debug then -local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") -local DebugInfoFrom=BASE.Debug.getinfo(3,"l") -local Function="function" -if DebugInfoCurrent.name then -Function=DebugInfoCurrent.name -end -local LineCurrent=DebugInfoCurrent.currentline -local LineFrom=-1 -if DebugInfoFrom then -LineFrom=DebugInfoFrom.currentline -end -env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s(%s)",LineCurrent,LineFrom,"I",self.ClassName,self.ClassID,Function,UTILS.BasicSerialize(Arguments))) -else -env.info(string.format("%1s:%30s%05d(%s)","I",self.ClassName,self.ClassID,UTILS.BasicSerialize(Arguments))) -end -end -ASTAR={ -ClassName="ASTAR", -Debug=nil, -lid=nil, -nodes={}, -counter=1, -Nnodes=0, -ncost=0, -ncostcache=0, -nvalid=0, -nvalidcache=0, -} -ASTAR.INF=1/0 -ASTAR.version="0.4.0" -function ASTAR:New() -local self=BASE:Inherit(self,BASE:New()) -self.lid="ASTAR | " -return self -end -function ASTAR:SetStartCoordinate(Coordinate) -self.startCoord=Coordinate -return self -end -function ASTAR:SetEndCoordinate(Coordinate) -self.endCoord=Coordinate -return self -end -function ASTAR:GetNodeFromCoordinate(Coordinate) -local node={} -node.coordinate=Coordinate -node.surfacetype=Coordinate:GetSurfaceType() -node.id=self.counter -node.valid={} -node.cost={} -self.counter=self.counter+1 -return node -end -function ASTAR:AddNode(Node) -self.nodes[Node.id]=Node -self.Nnodes=self.Nnodes+1 -return self -end -function ASTAR:AddNodeFromCoordinate(Coordinate) -local node=self:GetNodeFromCoordinate(Coordinate) -self:AddNode(node) -return node -end -function ASTAR:CheckValidSurfaceType(Node,SurfaceTypes) -if SurfaceTypes then -if type(SurfaceTypes)~="table"then -SurfaceTypes={SurfaceTypes} -end -for _,surface in pairs(SurfaceTypes)do -if surface==Node.surfacetype then -return true -end -end -return false -else -return true -end -end -function ASTAR:SetValidNeighbourFunction(NeighbourFunction,...) -self.ValidNeighbourFunc=NeighbourFunction -self.ValidNeighbourArg={} -if arg then -self.ValidNeighbourArg=arg -end -return self -end -function ASTAR:SetValidNeighbourLoS(CorridorWidth) -self:SetValidNeighbourFunction(ASTAR.LoS,CorridorWidth) -return self -end -function ASTAR:SetValidNeighbourDistance(MaxDistance) -self:SetValidNeighbourFunction(ASTAR.DistMax,MaxDistance) -return self -end -function ASTAR:SetValidNeighbourRoad(MaxDistance) -self:SetValidNeighbourFunction(ASTAR.Road,MaxDistance) -return self -end -function ASTAR:SetCostFunction(CostFunction,...) -self.CostFunc=CostFunction -self.CostArg={} -if arg then -self.CostArg=arg -end -return self -end -function ASTAR:SetCostDist2D() -self:SetCostFunction(ASTAR.Dist2D) -return self -end -function ASTAR:SetCostDist3D() -self:SetCostFunction(ASTAR.Dist3D) -return self -end -function ASTAR:SetCostRoad() -self:SetCostFunction(ASTAR) -return self -end -function ASTAR:CreateGrid(ValidSurfaceTypes,BoxHY,SpaceX,deltaX,deltaY,MarkGrid) -local Dz=SpaceX or 10000 -local Dx=BoxHY and BoxHY/2 or 20000 -local dz=deltaX or 2000 -local dx=deltaY or dz -local angle=self.startCoord:HeadingTo(self.endCoord) -local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz -local co=COORDINATE:New(0,0,0) -local do1=co:Get2DDistance(self.startCoord) -local ho1=co:HeadingTo(self.startCoord) -local xmin=-Dx -local zmin=-Dz -local nz=dist/dz+1 -local nx=2*Dx/dx+1 -local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes",nx,nz,nx*nz) -self:T(self.lid..text) -for i=1,nx do -local x=xmin+dx*(i-1) -for j=1,nz do -local z=zmin+dz*(j-1) -local vec3=UTILS.Rotate2D({x=x,y=0,z=z},angle) -local c=COORDINATE:New(vec3.z,vec3.y,vec3.x):Translate(do1,ho1,true) -local node=self:GetNodeFromCoordinate(c) -if self:CheckValidSurfaceType(node,ValidSurfaceTypes)then -if MarkGrid then -c:MarkToAll(string.format("i=%d, j=%d surface=%d",i,j,node.surfacetype)) -end -self:AddNode(node) -end -end -end -local text=string.format("Done building grid!") -self:T2(self.lid..text) -return self -end -function ASTAR.LoS(nodeA,nodeB,corridor) -local offset=1 -local dx=corridor and corridor/2 or nil -local dy=dx -local cA=nodeA.coordinate:GetVec3() -local cB=nodeB.coordinate:GetVec3() -cA.y=offset -cB.y=offset -local los=land.isVisible(cA,cB) -if los and corridor then -local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate) -local Ap=UTILS.VecTranslate(cA,dx,heading+90) -local Bp=UTILS.VecTranslate(cB,dx,heading+90) -los=land.isVisible(Ap,Bp) -if los then -local Am=UTILS.VecTranslate(cA,dx,heading-90) -local Bm=UTILS.VecTranslate(cB,dx,heading-90) -los=land.isVisible(Am,Bm) -end -end -return los -end -function ASTAR.Road(nodeA,nodeB) -local path=land.findPathOnRoads("roads",nodeA.coordinate.x,nodeA.coordinate.z,nodeB.coordinate.x,nodeB.coordinate.z) -if path then -return true -else -return false -end -end -function ASTAR.DistMax(nodeA,nodeB,distmax) -distmax=distmax or 2000 -local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate) -return dist<=distmax -end -function ASTAR.Dist2D(nodeA,nodeB) -local dist=nodeA.coordinate:Get2DDistance(nodeB) -return dist -end -function ASTAR.Dist3D(nodeA,nodeB) -local dist=nodeA.coordinate:Get3DDistance(nodeB.coordinate) -return dist -end -function ASTAR.DistRoad(nodeA,nodeB) -local path=land.findPathOnRoads("roads",nodeA.coordinate.x,nodeA.coordinate.z,nodeB.coordinate.x,nodeB.coordinate.z) -if path then -local dist=0 -for i=2,#path do -local b=path[i] -local a=path[i-1] -dist=dist+UTILS.VecDist2D(a,b) -end -return dist -end -return math.huge -end -function ASTAR:FindClosestNode(Coordinate) -local distMin=math.huge -local closeNode=nil -for _,_node in pairs(self.nodes)do -local node=_node -local dist=node.coordinate:Get2DDistance(Coordinate) -if dist1000 then -self:T(self.lid.."Adding start node to node grid!") -self:AddNode(node) -end -return self -end -function ASTAR:FindEndNode() -local node,dist=self:FindClosestNode(self.endCoord) -self.endNode=node -if dist>1000 then -self:T(self.lid.."Adding end node to node grid!") -self:AddNode(node) -end -return self -end -function ASTAR:GetPath(ExcludeStartNode,ExcludeEndNode) -self:FindStartNode() -self:FindEndNode() -local nodes=self.nodes -local start=self.startNode -local goal=self.endNode -local openset={} -local closedset={} -local came_from={} -local g_score={} -local f_score={} -openset[start.id]=true -local Nopen=1 -g_score[start.id]=0 -f_score[start.id]=g_score[start.id]+self:_HeuristicCost(start,goal) -local T0=timer.getAbsTime() -local text=string.format("Starting A* pathfinding with %d Nodes",self.Nnodes) -self:T(self.lid..text) -local Tstart=UTILS.GetOSTime() -while Nopen>0 do -local current=self:_LowestFscore(openset,f_score) -if current.id==goal.id then -local path=self:_UnwindPath({},came_from,goal) -if not ExcludeEndNode then -table.insert(path,goal) -end -if ExcludeStartNode then -table.remove(path,1) -end -local Tstop=UTILS.GetOSTime() -local dT=nil -if Tstart and Tstop then -dT=Tstop-Tstart -end -local text=string.format("Found path with %d nodes (%d total)",#path,self.Nnodes) -if dT then -text=text..string.format(", OS Time %.6f sec",dT) -end -text=text..string.format(", Nvalid=%d [%d cached]",self.nvalid,self.nvalidcache) -text=text..string.format(", Ncost=%d [%d cached]",self.ncost,self.ncostcache) -self:T(self.lid..text) -return path -end -openset[current.id]=nil -Nopen=Nopen-1 -closedset[current.id]=true -local neighbors=self:_NeighbourNodes(current,nodes) -for _,neighbor in pairs(neighbors)do -if self:_NotIn(closedset,neighbor.id)then -local tentative_g_score=g_score[current.id]+self:_DistNodes(current,neighbor) -if self:_NotIn(openset,neighbor.id)or tentative_g_score result=%s",tostring(isGen),tostring(isAny),tostring(isAll),tostring(self.negateResult),tostring(result))) -return result -end -function CONDITION:_EvalConditionsAll(functions) -local gotone=false -for _,_condition in pairs(functions or{})do -local condition=_condition -gotone=true -local istrue=condition.func(unpack(condition.arg)) -if not istrue then -return false -end -end -return true -end -function CONDITION:_EvalConditionsAny(functions) -local gotone=false -for _,_condition in pairs(functions or{})do -local condition=_condition -gotone=true -local istrue=condition.func(unpack(condition.arg)) -if istrue then -return true -end -end -if gotone then -return false -else -return true -end -end -function CONDITION:_CreateCondition(Ftype,Function,...) -self.functionCounter=self.functionCounter+1 -local condition={} -condition.uid=self.functionCounter -condition.type=Ftype or 0 -condition.persistence=self.defaultPersist -condition.func=Function -condition.arg={} -if arg then -condition.arg=arg -end -return condition -end -function CONDITION.IsTimeGreater(Time,Absolute) -local Tnow=nil -if Absolute then -Tnow=timer.getAbsTime() -else -Tnow=timer.getTime() -end -if Tnow>Time then -return true -else -return false -end -return nil -end -function CONDITION.IsRandomSuccess(Probability) -Probability=Probability or 50 -math.random() -math.random() -math.random() -local N=math.random()*100 -if N0 then -self:ScheduleOnce(Delay,USERFLAG.Set,self,Number) -else -trigger.action.setUserFlag(self.UserFlagName,Number) -end -return self -end -function USERFLAG:Get() -return trigger.misc.getUserFlag(self.UserFlagName) -end -function USERFLAG:Is(Number) -return trigger.misc.getUserFlag(self.UserFlagName)==Number -end -end -REPORT={ -ClassName="REPORT", -Title="", -} -function REPORT:New(Title) -local self=BASE:Inherit(self,BASE:New()) -self.Report={} -self:SetTitle(Title or"") -self:SetIndent(3) -return self -end -function REPORT:HasText() -return#self.Report>0 -end -function REPORT:SetIndent(Indent) -self.Indent=Indent -return self -end -function REPORT:Add(Text) -self.Report[#self.Report+1]=Text -return self -end -function REPORT:AddIndent(Text,Separator) -self.Report[#self.Report+1]=((Separator and Separator..string.rep(" ",self.Indent-1))or string.rep(" ",self.Indent))..Text:gsub("\n","\n"..string.rep(" ",self.Indent)) -return self -end -function REPORT:Text(Delimiter) -Delimiter=Delimiter or"\n" -local ReportText=(self.Title~=""and self.Title..Delimiter or self.Title)..table.concat(self.Report,Delimiter)or"" -return ReportText -end -function REPORT:SetTitle(Title) -self.Title=Title -return self -end -function REPORT:GetCount() -return#self.Report -end -SCHEDULER={ -ClassName="SCHEDULER", -Schedules={}, -MasterObject=nil, -ShowTrace=nil, -} -function SCHEDULER:New(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop) -local self=BASE:Inherit(self,BASE:New()) -self:F2({Start,Repeat,RandomizeFactor,Stop}) -local ScheduleID=nil -self.MasterObject=MasterObject -self.ShowTrace=false -if SchedulerFunction then -ScheduleID=self:Schedule(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop,3) -end -return self,ScheduleID -end -function SCHEDULER:Schedule(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop,TraceLevel,Fsm) -self:F2({Start,Repeat,RandomizeFactor,Stop}) -self:T3({SchedulerArguments}) -local ObjectName="-" -if MasterObject and MasterObject.ClassName and MasterObject.ClassID then -ObjectName=MasterObject.ClassName..MasterObject.ClassID -end -self:F3({"Schedule :",ObjectName,tostring(MasterObject),Start,Repeat,RandomizeFactor,Stop}) -self.MasterObject=MasterObject -local ScheduleID=_SCHEDULEDISPATCHER:AddSchedule(self, -SchedulerFunction, -SchedulerArguments, -Start, -Repeat, -RandomizeFactor, -Stop, -TraceLevel or 3, -Fsm -) -self.Schedules[#self.Schedules+1]=ScheduleID -return ScheduleID -end -function SCHEDULER:Start(ScheduleID) -self:F3({ScheduleID}) -self:T(string.format("Starting scheduler ID=%s",tostring(ScheduleID))) -_SCHEDULEDISPATCHER:Start(self,ScheduleID) -end -function SCHEDULER:Stop(ScheduleID) -self:F3({ScheduleID}) -self:T(string.format("Stopping scheduler ID=%s",tostring(ScheduleID))) -_SCHEDULEDISPATCHER:Stop(self,ScheduleID) -end -function SCHEDULER:Remove(ScheduleID) -self:F3({ScheduleID}) -self:T(string.format("Removing scheduler ID=%s",tostring(ScheduleID))) -_SCHEDULEDISPATCHER:RemoveSchedule(self,ScheduleID) -end -function SCHEDULER:Clear() -self:F3() -self:T(string.format("Clearing scheduler")) -_SCHEDULEDISPATCHER:Clear(self) -end -function SCHEDULER:ShowTrace() -_SCHEDULEDISPATCHER:ShowTrace(self) -end -function SCHEDULER:NoTrace() -_SCHEDULEDISPATCHER:NoTrace(self) -end -SCHEDULEDISPATCHER={ -ClassName="SCHEDULEDISPATCHER", -CallID=0, -PersistentSchedulers={}, -ObjectSchedulers={}, -Schedule=nil, -} -function SCHEDULEDISPATCHER:New() -local self=BASE:Inherit(self,BASE:New()) -self:F3() -return self -end -function SCHEDULEDISPATCHER:AddSchedule(Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop,TraceLevel,Fsm) -self:F2({Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop,TraceLevel,Fsm}) -self.CallID=self.CallID+1 -local CallID=self.CallID.."#"..(Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID()or"")or"" -self:T2(string.format("Adding schedule #%d CallID=%s",self.CallID,CallID)) -self.PersistentSchedulers=self.PersistentSchedulers or{} -self.ObjectSchedulers=self.ObjectSchedulers or setmetatable({},{__mode="v"}) -if Scheduler.MasterObject then -self.ObjectSchedulers[CallID]=Scheduler -self:F3({CallID=CallID,ObjectScheduler=tostring(self.ObjectSchedulers[CallID]),MasterObject=tostring(Scheduler.MasterObject)}) -else -self.PersistentSchedulers[CallID]=Scheduler -self:F3({CallID=CallID,PersistentScheduler=self.PersistentSchedulers[CallID]}) -end -self.Schedule=self.Schedule or setmetatable({},{__mode="k"}) -self.Schedule[Scheduler]=self.Schedule[Scheduler]or{} -self.Schedule[Scheduler][CallID]={} -self.Schedule[Scheduler][CallID].Function=ScheduleFunction -self.Schedule[Scheduler][CallID].Arguments=ScheduleArguments -self.Schedule[Scheduler][CallID].StartTime=timer.getTime()+(Start or 0) -self.Schedule[Scheduler][CallID].Start=Start+0.001 -self.Schedule[Scheduler][CallID].Repeat=Repeat or 0 -self.Schedule[Scheduler][CallID].Randomize=Randomize or 0 -self.Schedule[Scheduler][CallID].Stop=Stop -local Info={} -if debug then -TraceLevel=TraceLevel or 2 -Info=debug.getinfo(TraceLevel,"nlS") -local name_fsm=debug.getinfo(TraceLevel-1,"n").name -if name_fsm then -Info.name=name_fsm -end -end -self:T3(self.Schedule[Scheduler][CallID]) -self.Schedule[Scheduler][CallID].CallHandler=function(Params) -local CallID=Params.CallID -local Info=Params.Info or{} -local Source=Info.source or"?" -local Line=Info.currentline or"?" -local Name=Info.name or"?" -local ErrorHandler=function(errmsg) -env.info("Error in timer function: "..errmsg) -if BASE.Debug~=nil then -env.info(BASE.Debug.traceback()) -end -return errmsg -end -local Scheduler=self.ObjectSchedulers[CallID] -if not Scheduler then -Scheduler=self.PersistentSchedulers[CallID] -end -if Scheduler then -local MasterObject=tostring(Scheduler.MasterObject) -local Schedule=self.Schedule[Scheduler][CallID] -local SchedulerObject=Scheduler.MasterObject -local ShowTrace=Scheduler.ShowTrace -local ScheduleFunction=Schedule.Function -local ScheduleArguments=Schedule.Arguments or{} -local Start=Schedule.Start -local Repeat=Schedule.Repeat or 0 -local Randomize=Schedule.Randomize or 0 -local Stop=Schedule.Stop or 0 -local ScheduleID=Schedule.ScheduleID -local Prefix=(Repeat==0)and"--->"or"+++>" -local Status,Result -if SchedulerObject then -local function Timer() -if ShowTrace then -SchedulerObject:T(Prefix..Name..":"..Line.." ("..Source..")") -end -return ScheduleFunction(SchedulerObject,unpack(ScheduleArguments)) -end -Status,Result=xpcall(Timer,ErrorHandler) -else -local function Timer() -if ShowTrace then -self:T(Prefix..Name..":"..Line.." ("..Source..")") -end -return ScheduleFunction(unpack(ScheduleArguments)) -end -Status,Result=xpcall(Timer,ErrorHandler) -end -local CurrentTime=timer.getTime() -local StartTime=Schedule.StartTime -self:F3({CallID=CallID,ScheduleID=ScheduleID,Master=MasterObject,CurrentTime=CurrentTime,StartTime=StartTime,Start=Start,Repeat=Repeat,Randomize=Randomize,Stop=Stop}) -if Status and((Result==nil)or(Result and Result~=false))then -if Repeat~=0 and((Stop==0)or(Stop~=0 and CurrentTime<=StartTime+Stop))then -local ScheduleTime=CurrentTime+Repeat+math.random(-(Randomize*Repeat/2),(Randomize*Repeat/2))+0.0001 -return ScheduleTime -else -self:Stop(Scheduler,CallID) -end -else -self:Stop(Scheduler,CallID) -end -else -self:I("<<<>"..Name..":"..Line.." ("..Source..")") -end -return nil -end -self:Start(Scheduler,CallID,Info) -return CallID -end -function SCHEDULEDISPATCHER:RemoveSchedule(Scheduler,CallID) -self:F2({Remove=CallID,Scheduler=Scheduler}) -if CallID then -self:Stop(Scheduler,CallID) -self.Schedule[Scheduler][CallID]=nil -end -end -function SCHEDULEDISPATCHER:Start(Scheduler,CallID,Info) -self:F2({Start=CallID,Scheduler=Scheduler}) -if CallID then -local Schedule=self.Schedule[Scheduler][CallID] -if not Schedule.ScheduleID then -local Tnow=timer.getTime() -Schedule.StartTime=Tnow -Schedule.ScheduleID=timer.scheduleFunction(Schedule.CallHandler,{CallID=CallID,Info=Info},Tnow+Schedule.Start) -self:T(string.format("Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s",tostring(CallID),tostring(Schedule.ScheduleID))) -end -else -for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do -self:Start(Scheduler,CallID,Info) -end -end -end -function SCHEDULEDISPATCHER:Stop(Scheduler,CallID) -self:F2({Stop=CallID,Scheduler=Scheduler}) -if CallID then -local Schedule=self.Schedule[Scheduler][CallID] -if Schedule.ScheduleID then -self:T(string.format("SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s",tostring(CallID),tostring(Schedule.ScheduleID))) -timer.removeFunction(Schedule.ScheduleID) -Schedule.ScheduleID=nil -else -self:T(string.format("Error no ScheduleID for CallID=%s",tostring(CallID))) -end -else -for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do -self:Stop(Scheduler,CallID) -end -end -end -function SCHEDULEDISPATCHER:Clear(Scheduler) -self:F2({Scheduler=Scheduler}) -for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do -self:Stop(Scheduler,CallID) -end -end -function SCHEDULEDISPATCHER:ShowTrace(Scheduler) -self:F2({Scheduler=Scheduler}) -Scheduler.ShowTrace=true -end -function SCHEDULEDISPATCHER:NoTrace(Scheduler) -self:F2({Scheduler=Scheduler}) -Scheduler.ShowTrace=false -end -EVENT={ -ClassName="EVENT", -ClassID=0, -MissionEnd=false, -} -world.event.S_EVENT_NEW_CARGO=world.event.S_EVENT_MAX+1000 -world.event.S_EVENT_DELETE_CARGO=world.event.S_EVENT_MAX+1001 -world.event.S_EVENT_NEW_ZONE=world.event.S_EVENT_MAX+1002 -world.event.S_EVENT_DELETE_ZONE=world.event.S_EVENT_MAX+1003 -world.event.S_EVENT_NEW_ZONE_GOAL=world.event.S_EVENT_MAX+1004 -world.event.S_EVENT_DELETE_ZONE_GOAL=world.event.S_EVENT_MAX+1005 -world.event.S_EVENT_REMOVE_UNIT=world.event.S_EVENT_MAX+1006 -world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT=world.event.S_EVENT_MAX+1007 -EVENTS={ -Shot=world.event.S_EVENT_SHOT, -Hit=world.event.S_EVENT_HIT, -Takeoff=world.event.S_EVENT_TAKEOFF, -Land=world.event.S_EVENT_LAND, -Crash=world.event.S_EVENT_CRASH, -Ejection=world.event.S_EVENT_EJECTION, -Refueling=world.event.S_EVENT_REFUELING, -Dead=world.event.S_EVENT_DEAD, -PilotDead=world.event.S_EVENT_PILOT_DEAD, -BaseCaptured=world.event.S_EVENT_BASE_CAPTURED, -MissionStart=world.event.S_EVENT_MISSION_START, -MissionEnd=world.event.S_EVENT_MISSION_END, -TookControl=world.event.S_EVENT_TOOK_CONTROL, -RefuelingStop=world.event.S_EVENT_REFUELING_STOP, -Birth=world.event.S_EVENT_BIRTH, -HumanFailure=world.event.S_EVENT_HUMAN_FAILURE, -EngineStartup=world.event.S_EVENT_ENGINE_STARTUP, -EngineShutdown=world.event.S_EVENT_ENGINE_SHUTDOWN, -PlayerEnterUnit=world.event.S_EVENT_PLAYER_ENTER_UNIT, -PlayerLeaveUnit=world.event.S_EVENT_PLAYER_LEAVE_UNIT, -PlayerComment=world.event.S_EVENT_PLAYER_COMMENT, -ShootingStart=world.event.S_EVENT_SHOOTING_START, -ShootingEnd=world.event.S_EVENT_SHOOTING_END, -MarkAdded=world.event.S_EVENT_MARK_ADDED, -MarkChange=world.event.S_EVENT_MARK_CHANGE, -MarkRemoved=world.event.S_EVENT_MARK_REMOVED, -NewCargo=world.event.S_EVENT_NEW_CARGO, -DeleteCargo=world.event.S_EVENT_DELETE_CARGO, -NewZone=world.event.S_EVENT_NEW_ZONE, -DeleteZone=world.event.S_EVENT_DELETE_ZONE, -NewZoneGoal=world.event.S_EVENT_NEW_ZONE_GOAL, -DeleteZoneGoal=world.event.S_EVENT_DELETE_ZONE_GOAL, -RemoveUnit=world.event.S_EVENT_REMOVE_UNIT, -PlayerEnterAircraft=world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT, -DetailedFailure=world.event.S_EVENT_DETAILED_FAILURE or-1, -Kill=world.event.S_EVENT_KILL or-1, -Score=world.event.S_EVENT_SCORE or-1, -UnitLost=world.event.S_EVENT_UNIT_LOST or-1, -LandingAfterEjection=world.event.S_EVENT_LANDING_AFTER_EJECTION or-1, -ParatrooperLanding=world.event.S_EVENT_PARATROOPER_LENDING or-1, -DiscardChairAfterEjection=world.event.S_EVENT_DISCARD_CHAIR_AFTER_EJECTION or-1, -WeaponAdd=world.event.S_EVENT_WEAPON_ADD or-1, -TriggerZone=world.event.S_EVENT_TRIGGER_ZONE or-1, -LandingQualityMark=world.event.S_EVENT_LANDING_QUALITY_MARK or-1, -BDA=world.event.S_EVENT_BDA or-1, -AIAbortMission=world.event.S_EVENT_AI_ABORT_MISSION or-1, -DayNight=world.event.S_EVENT_DAYNIGHT or-1, -FlightTime=world.event.S_EVENT_FLIGHT_TIME or-1, -SelfKillPilot=world.event.S_EVENT_PLAYER_SELF_KILL_PILOT or-1, -PlayerCaptureAirfield=world.event.S_EVENT_PLAYER_CAPTURE_AIRFIELD or-1, -EmergencyLanding=world.event.S_EVENT_EMERGENCY_LANDING or-1, -UnitCreateTask=world.event.S_EVENT_UNIT_CREATE_TASK or-1, -UnitDeleteTask=world.event.S_EVENT_UNIT_DELETE_TASK or-1, -SimulationStart=world.event.S_EVENT_SIMULATION_START or-1, -WeaponRearm=world.event.S_EVENT_WEAPON_REARM or-1, -WeaponDrop=world.event.S_EVENT_WEAPON_DROP or-1, -UnitTaskTimeout=world.event.S_EVENT_UNIT_TASK_TIMEOUT or-1, -UnitTaskStage=world.event.S_EVENT_UNIT_TASK_STAGE or-1, -MacSubtaskScore=world.event.S_EVENT_MAC_SUBTASK_SCORE or-1, -MacExtraScore=world.event.S_EVENT_MAC_EXTRA_SCORE or-1, -MissionRestart=world.event.S_EVENT_MISSION_RESTART or-1, -MissionWinner=world.event.S_EVENT_MISSION_WINNER or-1, -PostponedTakeoff=world.event.S_EVENT_POSTPONED_TAKEOFF or-1, -PostponedLand=world.event.S_EVENT_POSTPONED_LAND or-1, -} -local _EVENTMETA={ -[world.event.S_EVENT_SHOT]={ -Order=1, -Side="I", -Event="OnEventShot", -Text="S_EVENT_SHOT" -}, -[world.event.S_EVENT_HIT]={ -Order=1, -Side="T", -Event="OnEventHit", -Text="S_EVENT_HIT" -}, -[world.event.S_EVENT_TAKEOFF]={ -Order=1, -Side="I", -Event="OnEventTakeoff", -Text="S_EVENT_TAKEOFF" -}, -[world.event.S_EVENT_LAND]={ -Order=1, -Side="I", -Event="OnEventLand", -Text="S_EVENT_LAND" -}, -[world.event.S_EVENT_CRASH]={ -Order=-1, -Side="I", -Event="OnEventCrash", -Text="S_EVENT_CRASH" -}, -[world.event.S_EVENT_EJECTION]={ -Order=1, -Side="I", -Event="OnEventEjection", -Text="S_EVENT_EJECTION" -}, -[world.event.S_EVENT_REFUELING]={ -Order=1, -Side="I", -Event="OnEventRefueling", -Text="S_EVENT_REFUELING" -}, -[world.event.S_EVENT_DEAD]={ -Order=-1, -Side="I", -Event="OnEventDead", -Text="S_EVENT_DEAD" -}, -[world.event.S_EVENT_PILOT_DEAD]={ -Order=1, -Side="I", -Event="OnEventPilotDead", -Text="S_EVENT_PILOT_DEAD" -}, -[world.event.S_EVENT_BASE_CAPTURED]={ -Order=1, -Side="I", -Event="OnEventBaseCaptured", -Text="S_EVENT_BASE_CAPTURED" -}, -[world.event.S_EVENT_MISSION_START]={ -Order=1, -Side="N", -Event="OnEventMissionStart", -Text="S_EVENT_MISSION_START" -}, -[world.event.S_EVENT_MISSION_END]={ -Order=1, -Side="N", -Event="OnEventMissionEnd", -Text="S_EVENT_MISSION_END" -}, -[world.event.S_EVENT_TOOK_CONTROL]={ -Order=1, -Side="N", -Event="OnEventTookControl", -Text="S_EVENT_TOOK_CONTROL" -}, -[world.event.S_EVENT_REFUELING_STOP]={ -Order=1, -Side="I", -Event="OnEventRefuelingStop", -Text="S_EVENT_REFUELING_STOP" -}, -[world.event.S_EVENT_BIRTH]={ -Order=1, -Side="I", -Event="OnEventBirth", -Text="S_EVENT_BIRTH" -}, -[world.event.S_EVENT_HUMAN_FAILURE]={ -Order=1, -Side="I", -Event="OnEventHumanFailure", -Text="S_EVENT_HUMAN_FAILURE" -}, -[world.event.S_EVENT_ENGINE_STARTUP]={ -Order=1, -Side="I", -Event="OnEventEngineStartup", -Text="S_EVENT_ENGINE_STARTUP" -}, -[world.event.S_EVENT_ENGINE_SHUTDOWN]={ -Order=1, -Side="I", -Event="OnEventEngineShutdown", -Text="S_EVENT_ENGINE_SHUTDOWN" -}, -[world.event.S_EVENT_PLAYER_ENTER_UNIT]={ -Order=1, -Side="I", -Event="OnEventPlayerEnterUnit", -Text="S_EVENT_PLAYER_ENTER_UNIT" -}, -[world.event.S_EVENT_PLAYER_LEAVE_UNIT]={ -Order=-1, -Side="I", -Event="OnEventPlayerLeaveUnit", -Text="S_EVENT_PLAYER_LEAVE_UNIT" -}, -[world.event.S_EVENT_PLAYER_COMMENT]={ -Order=1, -Side="I", -Event="OnEventPlayerComment", -Text="S_EVENT_PLAYER_COMMENT" -}, -[world.event.S_EVENT_SHOOTING_START]={ -Order=1, -Side="I", -Event="OnEventShootingStart", -Text="S_EVENT_SHOOTING_START" -}, -[world.event.S_EVENT_SHOOTING_END]={ -Order=1, -Side="I", -Event="OnEventShootingEnd", -Text="S_EVENT_SHOOTING_END" -}, -[world.event.S_EVENT_MARK_ADDED]={ -Order=1, -Side="I", -Event="OnEventMarkAdded", -Text="S_EVENT_MARK_ADDED" -}, -[world.event.S_EVENT_MARK_CHANGE]={ -Order=1, -Side="I", -Event="OnEventMarkChange", -Text="S_EVENT_MARK_CHANGE" -}, -[world.event.S_EVENT_MARK_REMOVED]={ -Order=1, -Side="I", -Event="OnEventMarkRemoved", -Text="S_EVENT_MARK_REMOVED" -}, -[EVENTS.NewCargo]={ -Order=1, -Event="OnEventNewCargo", -Text="S_EVENT_NEW_CARGO" -}, -[EVENTS.DeleteCargo]={ -Order=1, -Event="OnEventDeleteCargo", -Text="S_EVENT_DELETE_CARGO" -}, -[EVENTS.NewZone]={ -Order=1, -Event="OnEventNewZone", -Text="S_EVENT_NEW_ZONE" -}, -[EVENTS.DeleteZone]={ -Order=1, -Event="OnEventDeleteZone", -Text="S_EVENT_DELETE_ZONE" -}, -[EVENTS.NewZoneGoal]={ -Order=1, -Event="OnEventNewZoneGoal", -Text="S_EVENT_NEW_ZONE_GOAL" -}, -[EVENTS.DeleteZoneGoal]={ -Order=1, -Event="OnEventDeleteZoneGoal", -Text="S_EVENT_DELETE_ZONE_GOAL" -}, -[EVENTS.RemoveUnit]={ -Order=-1, -Event="OnEventRemoveUnit", -Text="S_EVENT_REMOVE_UNIT" -}, -[EVENTS.PlayerEnterAircraft]={ -Order=1, -Event="OnEventPlayerEnterAircraft", -Text="S_EVENT_PLAYER_ENTER_AIRCRAFT" -}, -[EVENTS.DetailedFailure]={ -Order=1, -Event="OnEventDetailedFailure", -Text="S_EVENT_DETAILED_FAILURE" -}, -[EVENTS.Kill]={ -Order=1, -Event="OnEventKill", -Text="S_EVENT_KILL" -}, -[EVENTS.Score]={ -Order=1, -Event="OnEventScore", -Text="S_EVENT_SCORE" -}, -[EVENTS.UnitLost]={ -Order=1, -Event="OnEventUnitLost", -Text="S_EVENT_UNIT_LOST" -}, -[EVENTS.LandingAfterEjection]={ -Order=1, -Event="OnEventLandingAfterEjection", -Text="S_EVENT_LANDING_AFTER_EJECTION" -}, -[EVENTS.ParatrooperLanding]={ -Order=1, -Event="OnEventParatrooperLanding", -Text="S_EVENT_PARATROOPER_LENDING" -}, -[EVENTS.DiscardChairAfterEjection]={ -Order=1, -Event="OnEventDiscardChairAfterEjection", -Text="S_EVENT_DISCARD_CHAIR_AFTER_EJECTION" -}, -[EVENTS.WeaponAdd]={ -Order=1, -Event="OnEventWeaponAdd", -Text="S_EVENT_WEAPON_ADD" -}, -[EVENTS.TriggerZone]={ -Order=1, -Event="OnEventTriggerZone", -Text="S_EVENT_TRIGGER_ZONE" -}, -[EVENTS.LandingQualityMark]={ -Order=1, -Event="OnEventLandingQualityMark", -Text="S_EVENT_LANDING_QUALITYMARK" -}, -[EVENTS.BDA]={ -Order=1, -Event="OnEventBDA", -Text="S_EVENT_BDA" -}, -[EVENTS.AIAbortMission]={ -Order=1, -Side="I", -Event="OnEventAIAbortMission", -Text="S_EVENT_AI_ABORT_MISSION" -}, -[EVENTS.DayNight]={ -Order=1, -Event="OnEventDayNight", -Text="S_EVENT_DAYNIGHT" -}, -[EVENTS.FlightTime]={ -Order=1, -Event="OnEventFlightTime", -Text="S_EVENT_FLIGHT_TIME" -}, -[EVENTS.SelfKillPilot]={ -Order=1, -Side="I", -Event="OnEventSelfKillPilot", -Text="S_EVENT_PLAYER_SELF_KILL_PILOT" -}, -[EVENTS.PlayerCaptureAirfield]={ -Order=1, -Event="OnEventPlayerCaptureAirfield", -Text="S_EVENT_PLAYER_CAPTURE_AIRFIELD" -}, -[EVENTS.EmergencyLanding]={ -Order=1, -Side="I", -Event="OnEventEmergencyLanding", -Text="S_EVENT_EMERGENCY_LANDING" -}, -[EVENTS.UnitCreateTask]={ -Order=1, -Event="OnEventUnitCreateTask", -Text="S_EVENT_UNIT_CREATE_TASK" -}, -[EVENTS.UnitDeleteTask]={ -Order=1, -Event="OnEventUnitDeleteTask", -Text="S_EVENT_UNIT_DELETE_TASK" -}, -[EVENTS.SimulationStart]={ -Order=1, -Event="OnEventSimulationStart", -Text="S_EVENT_SIMULATION_START" -}, -[EVENTS.WeaponRearm]={ -Order=1, -Side="I", -Event="OnEventWeaponRearm", -Text="S_EVENT_WEAPON_REARM" -}, -[EVENTS.WeaponDrop]={ -Order=1, -Side="I", -Event="OnEventWeaponDrop", -Text="S_EVENT_WEAPON_DROP" -}, -[EVENTS.UnitTaskTimeout]={ -Order=1, -Side="I", -Event="OnEventUnitTaskTimeout", -Text="S_EVENT_UNIT_TASK_TIMEOUT " -}, -[EVENTS.UnitTaskStage]={ -Order=1, -Side="I", -Event="OnEventUnitTaskStage", -Text="S_EVENT_UNIT_TASK_STAGE " -}, -[EVENTS.MacSubtaskScore]={ -Order=1, -Side="I", -Event="OnEventMacSubtaskScore", -Text="S_EVENT_MAC_SUBTASK_SCORE" -}, -[EVENTS.MacExtraScore]={ -Order=1, -Side="I", -Event="OnEventMacExtraScore", -Text="S_EVENT_MAC_EXTRA_SCOREP" -}, -[EVENTS.MissionRestart]={ -Order=1, -Side="I", -Event="OnEventMissionRestart", -Text="S_EVENT_MISSION_RESTART" -}, -[EVENTS.MissionWinner]={ -Order=1, -Side="I", -Event="OnEventMissionWinner", -Text="S_EVENT_MISSION_WINNER" -}, -[EVENTS.PostponedTakeoff]={ -Order=1, -Side="I", -Event="OnEventPostponedTakeoff", -Text="S_EVENT_POSTPONED_TAKEOFF" -}, -[EVENTS.PostponedLand]={ -Order=1, -Side="I", -Event="OnEventPostponedLand", -Text="S_EVENT_POSTPONED_LAND" -}, -} -function EVENT:New() -local self=BASE:Inherit(self,BASE:New()) -self.EventHandler=world.addEventHandler(self) -return self -end -function EVENT:Init(EventID,EventClass) -self:F3({_EVENTMETA[EventID].Text,EventClass}) -if not self.Events[EventID]then -self.Events[EventID]={} -end -local EventPriority=EventClass:GetEventPriority() -if not self.Events[EventID][EventPriority]then -self.Events[EventID][EventPriority]=setmetatable({},{__mode="k"}) -end -if not self.Events[EventID][EventPriority][EventClass]then -self.Events[EventID][EventPriority][EventClass]={} -end -return self.Events[EventID][EventPriority][EventClass] -end -function EVENT:RemoveEvent(EventClass,EventID) -self:F2({"Removing subscription for class: ",EventClass:GetClassNameAndID()}) -local EventPriority=EventClass:GetEventPriority() -self.Events=self.Events or{} -self.Events[EventID]=self.Events[EventID]or{} -self.Events[EventID][EventPriority]=self.Events[EventID][EventPriority]or{} -self.Events[EventID][EventPriority][EventClass]=nil -return self -end -function EVENT:Reset(EventObject) -self:F({"Resetting subscriptions for class: ",EventObject:GetClassNameAndID()}) -local EventPriority=EventObject:GetEventPriority() -for EventID,EventData in pairs(self.Events)do -if self.EventsDead then -if self.EventsDead[EventID]then -if self.EventsDead[EventID][EventPriority]then -if self.EventsDead[EventID][EventPriority][EventObject]then -self.Events[EventID][EventPriority][EventObject]=self.EventsDead[EventID][EventPriority][EventObject] -end -end -end -end -end -end -function EVENT:RemoveAll(EventClass) -local EventClassName=EventClass:GetClassNameAndID() -local EventPriority=EventClass:GetEventPriority() -for EventID,EventData in pairs(self.Events)do -self.Events[EventID][EventPriority][EventClass]=nil -end -return self -end -function EVENT:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EventID) -self:F2(EventTemplate.name) -for EventUnitID,EventUnit in pairs(EventTemplate.units)do -self:OnEventForUnit(EventUnit.name,EventFunction,EventClass,EventID) -end -return self -end -function EVENT:OnEventGeneric(EventFunction,EventClass,EventID) -self:F2({EventID,EventClass,EventFunction}) -local EventData=self:Init(EventID,EventClass) -EventData.EventFunction=EventFunction -return self -end -function EVENT:OnEventForUnit(UnitName,EventFunction,EventClass,EventID) -self:F2(UnitName) -local EventData=self:Init(EventID,EventClass) -EventData.EventUnit=true -EventData.EventFunction=EventFunction -return self -end -function EVENT:OnEventForGroup(GroupName,EventFunction,EventClass,EventID,...) -local Event=self:Init(EventID,EventClass) -Event.EventGroup=true -Event.EventFunction=EventFunction -Event.Params=arg -return self -end -do -function EVENT:OnBirthForTemplate(EventTemplate,EventFunction,EventClass) -self:F2(EventTemplate.name) -self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Birth) -return self -end -end -do -function EVENT:OnCrashForTemplate(EventTemplate,EventFunction,EventClass) -self:F2(EventTemplate.name) -self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Crash) -return self -end -end -do -function EVENT:OnDeadForTemplate(EventTemplate,EventFunction,EventClass) -self:F2(EventTemplate.name) -self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Dead) -return self -end -end -do -function EVENT:OnLandForTemplate(EventTemplate,EventFunction,EventClass) -self:F2(EventTemplate.name) -self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Land) -return self -end -end -do -function EVENT:OnTakeOffForTemplate(EventTemplate,EventFunction,EventClass) -self:F2(EventTemplate.name) -self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Takeoff) -return self -end -end -do -function EVENT:OnEngineShutDownForTemplate(EventTemplate,EventFunction,EventClass) -self:F2(EventTemplate.name) -self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.EngineShutdown) -return self -end -end -do -function EVENT:CreateEventNewCargo(Cargo) -self:F({Cargo}) -local Event={ -id=EVENTS.NewCargo, -time=timer.getTime(), -cargo=Cargo, -} -world.onEvent(Event) -end -function EVENT:CreateEventDeleteCargo(Cargo) -self:F({Cargo}) -local Event={ -id=EVENTS.DeleteCargo, -time=timer.getTime(), -cargo=Cargo, -} -world.onEvent(Event) -end -function EVENT:CreateEventNewZone(Zone) -self:F({Zone}) -local Event={ -id=EVENTS.NewZone, -time=timer.getTime(), -zone=Zone, -} -world.onEvent(Event) -end -function EVENT:CreateEventDeleteZone(Zone) -self:F({Zone}) -local Event={ -id=EVENTS.DeleteZone, -time=timer.getTime(), -zone=Zone, -} -world.onEvent(Event) -end -function EVENT:CreateEventNewZoneGoal(ZoneGoal) -self:F({ZoneGoal}) -local Event={ -id=EVENTS.NewZoneGoal, -time=timer.getTime(), -ZoneGoal=ZoneGoal, -} -world.onEvent(Event) -end -function EVENT:CreateEventDeleteZoneGoal(ZoneGoal) -self:F({ZoneGoal}) -local Event={ -id=EVENTS.DeleteZoneGoal, -time=timer.getTime(), -ZoneGoal=ZoneGoal, -} -world.onEvent(Event) -end -function EVENT:CreateEventPlayerEnterUnit(PlayerUnit) -self:F({PlayerUnit}) -local Event={ -id=EVENTS.PlayerEnterUnit, -time=timer.getTime(), -initiator=PlayerUnit:GetDCSObject() -} -world.onEvent(Event) -end -function EVENT:CreateEventPlayerEnterAircraft(PlayerUnit) -self:F({PlayerUnit}) -local Event={ -id=EVENTS.PlayerEnterAircraft, -time=timer.getTime(), -initiator=PlayerUnit:GetDCSObject() -} -world.onEvent(Event) -end -end -function EVENT:onEvent(Event) -local ErrorHandler=function(errmsg) -env.info("Error in SCHEDULER function:"..errmsg) -if BASE.Debug~=nil then -env.info(debug.traceback()) -end -return errmsg -end -local EventMeta=_EVENTMETA[Event.id] -if EventMeta then -if self and self.Events and self.Events[Event.id]and self.MissionEnd==false and(Event.initiator~=nil or(Event.initiator==nil and Event.id~=EVENTS.PlayerLeaveUnit))then -if Event.id and Event.id==EVENTS.MissionEnd then -self.MissionEnd=true -end -if Event.initiator then -Event.IniObjectCategory=Object.getCategory(Event.initiator) -if Event.IniObjectCategory==Object.Category.STATIC then -if Event.id==31 then -Event.IniDCSUnit=Event.initiator -local ID=Event.initiator.id_ -Event.IniDCSUnitName=string.format("Ejected Pilot ID %s",tostring(ID)) -Event.IniUnitName=Event.IniDCSUnitName -Event.IniCoalition=0 -Event.IniCategory=0 -Event.IniTypeName="Ejected Pilot" -elseif Event.id==33 then -Event.IniDCSUnit=Event.initiator -local ID=Event.initiator.id_ -Event.IniDCSUnitName=string.format("Ejection Seat ID %s",tostring(ID)) -Event.IniUnitName=Event.IniDCSUnitName -Event.IniCoalition=0 -Event.IniCategory=0 -Event.IniTypeName="Ejection Seat" -else -Event.IniDCSUnit=Event.initiator -Event.IniDCSUnitName=Event.IniDCSUnit:getName() -Event.IniUnitName=Event.IniDCSUnitName -Event.IniUnit=STATIC:FindByName(Event.IniDCSUnitName,false) -Event.IniCoalition=Event.IniDCSUnit:getCoalition() -Event.IniCategory=Event.IniDCSUnit:getDesc().category -Event.IniTypeName=Event.IniDCSUnit:getTypeName() -end -local Unit=UNIT:FindByName(Event.IniDCSUnitName) -if Unit then -Event.IniObjectCategory=Object.Category.UNIT -end -elseif Event.IniObjectCategory==Object.Category.UNIT then -Event.IniDCSUnit=Event.initiator -Event.IniDCSUnitName=Event.IniDCSUnit:getName() -Event.IniUnitName=Event.IniDCSUnitName -Event.IniDCSGroup=Event.IniDCSUnit:getGroup() -Event.IniUnit=UNIT:FindByName(Event.IniDCSUnitName) -if not Event.IniUnit then -Event.IniUnit=CLIENT:FindByName(Event.IniDCSUnitName,'',true) -end -Event.IniDCSGroupName=Event.IniUnit and Event.IniUnit.GroupName or"" -if Event.IniDCSGroup and Event.IniDCSGroup:isExist()then -Event.IniDCSGroupName=Event.IniDCSGroup:getName() -Event.IniGroup=GROUP:FindByName(Event.IniDCSGroupName) -Event.IniGroupName=Event.IniDCSGroupName -end -Event.IniPlayerName=Event.IniDCSUnit:getPlayerName() -if Event.IniPlayerName then -local PID=NET.GetPlayerIDByName(nil,Event.IniPlayerName) -if PID then -Event.IniPlayerUCID=net.get_player_info(tonumber(PID),'ucid') -end -end -Event.IniCoalition=Event.IniDCSUnit:getCoalition() -Event.IniTypeName=Event.IniDCSUnit:getTypeName() -Event.IniCategory=Event.IniDCSUnit:getDesc().category -elseif Event.IniObjectCategory==Object.Category.CARGO then -Event.IniDCSUnit=Event.initiator -Event.IniDCSUnitName=Event.IniDCSUnit:getName() -Event.IniUnitName=Event.IniDCSUnitName -Event.IniUnit=CARGO:FindByName(Event.IniDCSUnitName) -Event.IniCoalition=Event.IniDCSUnit:getCoalition() -Event.IniCategory=Event.IniDCSUnit:getDesc().category -Event.IniTypeName=Event.IniDCSUnit:getTypeName() -elseif Event.IniObjectCategory==Object.Category.SCENERY then -Event.IniDCSUnit=Event.initiator -Event.IniDCSUnitName=Event.IniDCSUnit:getName() -Event.IniUnitName=Event.IniDCSUnitName -Event.IniUnit=SCENERY:Register(Event.IniDCSUnitName,Event.initiator) -Event.IniCategory=Event.IniDCSUnit:getDesc().category -Event.IniTypeName=Event.initiator:isExist()and Event.IniDCSUnit:getTypeName()or"SCENERY" -elseif Event.IniObjectCategory==Object.Category.BASE then -Event.IniDCSUnit=Event.initiator -Event.IniDCSUnitName=Event.IniDCSUnit:getName() -Event.IniUnitName=Event.IniDCSUnitName -Event.IniUnit=AIRBASE:FindByName(Event.IniDCSUnitName) -Event.IniCoalition=Event.IniDCSUnit:getCoalition() -Event.IniCategory=Event.IniDCSUnit:getDesc().category -Event.IniTypeName=Event.IniDCSUnit:getTypeName() -if not Event.IniUnit then -_DATABASE:_RegisterAirbase(Event.initiator) -Event.IniUnit=AIRBASE:FindByName(Event.IniDCSUnitName) -end -end -end -if Event.target then -Event.TgtObjectCategory=Object.getCategory(Event.target) -if Event.TgtObjectCategory==Object.Category.UNIT then -Event.TgtDCSUnit=Event.target -Event.TgtDCSGroup=Event.TgtDCSUnit:getGroup() -Event.TgtDCSUnitName=Event.TgtDCSUnit:getName() -Event.TgtUnitName=Event.TgtDCSUnitName -Event.TgtUnit=UNIT:FindByName(Event.TgtDCSUnitName) -Event.TgtDCSGroupName="" -if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist()then -Event.TgtDCSGroupName=Event.TgtDCSGroup:getName() -Event.TgtGroup=GROUP:FindByName(Event.TgtDCSGroupName) -Event.TgtGroupName=Event.TgtDCSGroupName -end -Event.TgtPlayerName=Event.TgtDCSUnit:getPlayerName() -if Event.TgtPlayerName then -local PID=NET.GetPlayerIDByName(nil,Event.TgtPlayerName) -if PID then -Event.TgtPlayerUCID=net.get_player_info(tonumber(PID),'ucid') -end -end -Event.TgtCoalition=Event.TgtDCSUnit:getCoalition() -Event.TgtCategory=Event.TgtDCSUnit:getDesc().category -Event.TgtTypeName=Event.TgtDCSUnit:getTypeName() -elseif Event.TgtObjectCategory==Object.Category.STATIC then -Event.TgtDCSUnit=Event.target -if Event.target:isExist()and Event.id~=33 then -Event.TgtDCSUnitName=Event.TgtDCSUnit:getName() -if Event.TgtDCSUnitName and Event.TgtDCSUnitName~=""then -Event.TgtUnitName=Event.TgtDCSUnitName -Event.TgtUnit=STATIC:FindByName(Event.TgtDCSUnitName,false) -Event.TgtCoalition=Event.TgtDCSUnit:getCoalition() -Event.TgtCategory=Event.TgtDCSUnit:getDesc().category -Event.TgtTypeName=Event.TgtDCSUnit:getTypeName() -end -else -Event.TgtDCSUnitName=string.format("No target object for Event ID %s",tostring(Event.id)) -Event.TgtUnitName=Event.TgtDCSUnitName -Event.TgtUnit=nil -Event.TgtCoalition=0 -Event.TgtCategory=0 -if Event.id==6 then -Event.TgtTypeName="Ejected Pilot" -Event.TgtDCSUnitName=string.format("Ejected Pilot ID %s",tostring(Event.IniDCSUnitName)) -Event.TgtUnitName=Event.TgtDCSUnitName -elseif Event.id==33 then -Event.TgtTypeName="Ejection Seat" -Event.TgtDCSUnitName=string.format("Ejection Seat ID %s",tostring(Event.IniDCSUnitName)) -Event.TgtUnitName=Event.TgtDCSUnitName -else -Event.TgtTypeName="Static" -end -end -elseif Event.TgtObjectCategory==Object.Category.SCENERY then -Event.TgtDCSUnit=Event.target -Event.TgtDCSUnitName=Event.TgtDCSUnit:getName() -Event.TgtUnitName=Event.TgtDCSUnitName -Event.TgtUnit=SCENERY:Register(Event.TgtDCSUnitName,Event.target) -Event.TgtCategory=Event.TgtDCSUnit:getDesc().category -Event.TgtTypeName=Event.TgtDCSUnit:getTypeName() -end -end -if Event.weapon then -Event.Weapon=Event.weapon -Event.WeaponName=Event.Weapon:getTypeName() -Event.WeaponUNIT=CLIENT:Find(Event.Weapon,'',true) -Event.WeaponPlayerName=Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName() -Event.WeaponCoalition=Event.WeaponUNIT and Event.Weapon:getCoalition() -Event.WeaponCategory=Event.WeaponUNIT and Event.Weapon:getDesc().category -Event.WeaponTypeName=Event.WeaponUNIT and Event.Weapon:getTypeName() -end -if Event.place then -if Event.id==EVENTS.LandingAfterEjection then -else -if Event.place:isExist()and Object.getCategory(Event.place)~=Object.Category.SCENERY then -Event.Place=AIRBASE:Find(Event.place) -Event.PlaceName=Event.Place:GetName() -end -end -end -if Event.idx then -Event.MarkID=Event.idx -Event.MarkVec3=Event.pos -Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos) -Event.MarkText=Event.text -Event.MarkCoalition=Event.coalition -Event.MarkGroupID=Event.groupID -end -if Event.cargo then -Event.Cargo=Event.cargo -Event.CargoName=Event.cargo.Name -end -if Event.zone then -Event.Zone=Event.zone -Event.ZoneName=Event.zone.ZoneName -end -local PriorityOrder=EventMeta.Order -local PriorityBegin=PriorityOrder==-1 and 5 or 1 -local PriorityEnd=PriorityOrder==-1 and 1 or 5 -for EventPriority=PriorityBegin,PriorityEnd,PriorityOrder do -if self.Events[Event.id][EventPriority]then -for EventClass,EventData in pairs(self.Events[Event.id][EventPriority])do -Event.IniGroup=Event.IniGroup or GROUP:FindByName(Event.IniDCSGroupName) -Event.TgtGroup=Event.TgtGroup or GROUP:FindByName(Event.TgtDCSGroupName) -if EventData.EventUnit then -if EventClass:IsAlive()or -Event.id==EVENTS.PlayerEnterUnit or -Event.id==EVENTS.Crash or -Event.id==EVENTS.Dead or -Event.id==EVENTS.RemoveUnit or -Event.id==EVENTS.UnitLost then -local UnitName=EventClass:GetName() -if(EventMeta.Side=="I"and UnitName==Event.IniDCSUnitName)or -(EventMeta.Side=="T"and UnitName==Event.TgtDCSUnitName)then -if EventData.EventFunction then -local Result,Value=xpcall( -function() -return EventData.EventFunction(EventClass,Event) -end,ErrorHandler) -else -local EventFunction=EventClass[EventMeta.Event] -if EventFunction and type(EventFunction)=="function"then -local Result,Value=xpcall( -function() -return EventFunction(EventClass,Event) -end,ErrorHandler) -end -end -end -else -self:RemoveEvent(EventClass,Event.id) -end -else -if EventData.EventGroup then -if EventClass:IsAlive()or -Event.id==EVENTS.PlayerEnterUnit or -Event.id==EVENTS.Crash or -Event.id==EVENTS.Dead or -Event.id==EVENTS.RemoveUnit or -Event.id==EVENTS.UnitLost then -local GroupName=EventClass:GetName() -if(EventMeta.Side=="I"and GroupName==Event.IniDCSGroupName)or -(EventMeta.Side=="T"and GroupName==Event.TgtDCSGroupName)then -if EventData.EventFunction then -local Result,Value=xpcall( -function() -return EventData.EventFunction(EventClass,Event,unpack(EventData.Params)) -end,ErrorHandler) -else -local EventFunction=EventClass[EventMeta.Event] -if EventFunction and type(EventFunction)=="function"then -local Result,Value=xpcall( -function() -return EventFunction(EventClass,Event,unpack(EventData.Params)) -end,ErrorHandler) -end -end -end -else -end -else -if not EventData.EventUnit then -if EventData.EventFunction then -local Result,Value=xpcall( -function() -return EventData.EventFunction(EventClass,Event) -end,ErrorHandler) -else -local EventFunction=EventClass[EventMeta.Event] -if EventFunction and type(EventFunction)=="function"then -local Result,Value=xpcall( -function() -local Result,Value=EventFunction(EventClass,Event) -return Result,Value -end,ErrorHandler) -end -end -end -end -end -end -end -end -if Event.id==EVENTS.DeleteCargo then -Event.Cargo.NoDestroy=nil -end -else -self:T({EventMeta.Text,Event}) -end -else -self:E(string.format("WARNING: Could not get EVENTMETA data for event ID=%d! Is this an unknown/new DCS event?",tostring(Event.id))) -end -Event=nil -end -EVENTHANDLER={ -ClassName="EVENTHANDLER", -ClassID=0, -} -function EVENTHANDLER:New() -self=BASE:Inherit(self,BASE:New()) -return self -end -SETTINGS={ -ClassName="SETTINGS", -ShowPlayerMenu=true, -MenuShort=false, -MenuStatic=false, -} -SETTINGS.__Enum={} -SETTINGS.__Enum.Era={ -WWII=1, -Korea=2, -Cold=3, -Modern=4, -} -do -function SETTINGS:Set(PlayerName) -if PlayerName==nil then -local self=BASE:Inherit(self,BASE:New()) -self:SetMetric() -self:SetA2G_BR() -self:SetA2A_BRAA() -self:SetLL_Accuracy(3) -self:SetMGRS_Accuracy(5) -self:SetMessageTime(MESSAGE.Type.Briefing,180) -self:SetMessageTime(MESSAGE.Type.Detailed,60) -self:SetMessageTime(MESSAGE.Type.Information,30) -self:SetMessageTime(MESSAGE.Type.Overview,60) -self:SetMessageTime(MESSAGE.Type.Update,15) -self:SetEraModern() -self:SetLocale("en") -return self -else -local Settings=_DATABASE:GetPlayerSettings(PlayerName) -if not Settings then -Settings=BASE:Inherit(self,BASE:New()) -_DATABASE:SetPlayerSettings(PlayerName,Settings) -end -return Settings -end -end -function SETTINGS:SetMenutextShort(onoff) -_SETTINGS.MenuShort=onoff -end -function SETTINGS:SetMenuStatic(onoff) -_SETTINGS.MenuStatic=onoff -end -function SETTINGS:SetMetric() -self.Metric=true -end -function SETTINGS:SetLocale(Locale) -self.Locale=Locale or"en" -end -function SETTINGS:GetLocale() -return self.Locale or _SETTINGS:GetLocale() -end -function SETTINGS:IsMetric() -return(self.Metric~=nil and self.Metric==true)or(self.Metric==nil and _SETTINGS:IsMetric()) -end -function SETTINGS:SetImperial() -self.Metric=false -end -function SETTINGS:IsImperial() -return(self.Metric~=nil and self.Metric==false)or(self.Metric==nil and _SETTINGS:IsImperial()) -end -function SETTINGS:SetLL_Accuracy(LL_Accuracy) -self.LL_Accuracy=LL_Accuracy -end -function SETTINGS:GetLL_DDM_Accuracy() -return self.LL_DDM_Accuracy or _SETTINGS:GetLL_DDM_Accuracy() -end -function SETTINGS:SetMGRS_Accuracy(MGRS_Accuracy) -self.MGRS_Accuracy=MGRS_Accuracy -end -function SETTINGS:GetMGRS_Accuracy() -return self.MGRS_Accuracy or _SETTINGS:GetMGRS_Accuracy() -end -function SETTINGS:SetMessageTime(MessageType,MessageTime) -self.MessageTypeTimings=self.MessageTypeTimings or{} -self.MessageTypeTimings[MessageType]=MessageTime -end -function SETTINGS:GetMessageTime(MessageType) -return(self.MessageTypeTimings and self.MessageTypeTimings[MessageType])or _SETTINGS:GetMessageTime(MessageType) -end -function SETTINGS:SetA2G_LL_DMS() -self.A2GSystem="LL DMS" -end -function SETTINGS:SetA2G_LL_DDM() -self.A2GSystem="LL DDM" -end -function SETTINGS:IsA2G_LL_DMS() -return(self.A2GSystem and self.A2GSystem=="LL DMS")or(not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS()) -end -function SETTINGS:IsA2G_LL_DDM() -return(self.A2GSystem and self.A2GSystem=="LL DDM")or(not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM()) -end -function SETTINGS:SetA2G_MGRS() -self.A2GSystem="MGRS" -end -function SETTINGS:IsA2G_MGRS() -return(self.A2GSystem and self.A2GSystem=="MGRS")or(not self.A2GSystem and _SETTINGS:IsA2G_MGRS()) -end -function SETTINGS:SetA2G_BR() -self.A2GSystem="BR" -end -function SETTINGS:IsA2G_BR() -return(self.A2GSystem and self.A2GSystem=="BR")or(not self.A2GSystem and _SETTINGS:IsA2G_BR()) -end -function SETTINGS:SetA2A_BRAA() -self.A2ASystem="BRAA" -end -function SETTINGS:IsA2A_BRAA() -return(self.A2ASystem and self.A2ASystem=="BRAA")or(not self.A2ASystem and _SETTINGS:IsA2A_BRAA()) -end -function SETTINGS:SetA2A_BULLS() -self.A2ASystem="BULLS" -end -function SETTINGS:IsA2A_BULLS() -return(self.A2ASystem and self.A2ASystem=="BULLS")or(not self.A2ASystem and _SETTINGS:IsA2A_BULLS()) -end -function SETTINGS:SetA2A_LL_DMS() -self.A2ASystem="LL DMS" -end -function SETTINGS:SetA2A_LL_DDM() -self.A2ASystem="LL DDM" -end -function SETTINGS:IsA2A_LL_DMS() -return(self.A2ASystem and self.A2ASystem=="LL DMS")or(not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS()) -end -function SETTINGS:IsA2A_LL_DDM() -return(self.A2ASystem and self.A2ASystem=="LL DDM")or(not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM()) -end -function SETTINGS:SetA2A_MGRS() -self.A2ASystem="MGRS" -end -function SETTINGS:IsA2A_MGRS() -return(self.A2ASystem and self.A2ASystem=="MGRS")or(not self.A2ASystem and _SETTINGS:IsA2A_MGRS()) -end -function SETTINGS:SetSystemMenu(MenuGroup,RootMenu) -local MenuText="System Settings" -local MenuTime=timer.getTime() -local SettingsMenu=MENU_GROUP:New(MenuGroup,MenuText,RootMenu):SetTime(MenuTime) -local text="A2G Coordinate System" -if _SETTINGS.MenuShort then -text="A2G Coordinates" -end -local A2GCoordinateMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime) -if not self:IsA2G_LL_DMS()then -local text="Lat/Lon Degree Min Sec (LL DMS)" -if _SETTINGS.MenuShort then -text="LL DMS" -end -MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime) -end -if not self:IsA2G_LL_DDM()then -local text="Lat/Lon Degree Dec Min (LL DDM)" -if _SETTINGS.MenuShort then -text="LL DDM" -end -MENU_GROUP_COMMAND:New(MenuGroup,"Lat/Lon Degree Dec Min (LL DDM)",A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime) -end -if self:IsA2G_LL_DDM()then -local text1="LL DDM Accuracy 1" -local text2="LL DDM Accuracy 2" -local text3="LL DDM Accuracy 3" -if _SETTINGS.MenuShort then -text1="LL DDM" -end -MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 1",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 2",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 3",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime) -end -if not self:IsA2G_BR()then -local text="Bearing, Range (BR)" -if _SETTINGS.MenuShort then -text="BR" -end -MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"BR"):SetTime(MenuTime) -end -if not self:IsA2G_MGRS()then -local text="Military Grid (MGRS)" -if _SETTINGS.MenuShort then -text="MGRS" -end -MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"MGRS"):SetTime(MenuTime) -end -if self:IsA2G_MGRS()then -MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 1",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 2",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 3",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 4",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,4):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 5",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,5):SetTime(MenuTime) -end -local text="A2A Coordinate System" -if _SETTINGS.MenuShort then -text="A2A Coordinates" -end -local A2ACoordinateMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime) -if not self:IsA2A_LL_DMS()then -local text="Lat/Lon Degree Min Sec (LL DMS)" -if _SETTINGS.MenuShort then -text="LL DMS" -end -MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime) -end -if not self:IsA2A_LL_DDM()then -local text="Lat/Lon Degree Dec Min (LL DDM)" -if _SETTINGS.MenuShort then -text="LL DDM" -end -MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime) -end -if self:IsA2A_LL_DDM()or self:IsA2A_LL_DMS()then -MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 0",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,0):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 1",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 2",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 3",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime) -end -if not self:IsA2A_BULLS()then -local text="Bullseye (BULLS)" -if _SETTINGS.MenuShort then -text="Bulls" -end -MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BULLS"):SetTime(MenuTime) -end -if not self:IsA2A_BRAA()then -local text="Bearing Range Altitude Aspect (BRAA)" -if _SETTINGS.MenuShort then -text="BRAA" -end -MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BRAA"):SetTime(MenuTime) -end -if not self:IsA2A_MGRS()then -local text="Military Grid (MGRS)" -if _SETTINGS.MenuShort then -text="MGRS" -end -MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"MGRS"):SetTime(MenuTime) -end -if self:IsA2A_MGRS()then -MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 1",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 2",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 3",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 4",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,4):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 5",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,5):SetTime(MenuTime) -end -local text="Measures and Weights System" -if _SETTINGS.MenuShort then -text="Unit System" -end -local MetricsMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime) -if self:IsMetric()then -local text="Imperial (Miles,Feet)" -if _SETTINGS.MenuShort then -text="Imperial" -end -MENU_GROUP_COMMAND:New(MenuGroup,text,MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,false):SetTime(MenuTime) -end -if self:IsImperial()then -local text="Metric (Kilometers,Meters)" -if _SETTINGS.MenuShort then -text="Metric" -end -MENU_GROUP_COMMAND:New(MenuGroup,text,MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,true):SetTime(MenuTime) -end -local text="Messages and Reports" -if _SETTINGS.MenuShort then -text="Messages & Reports" -end -local MessagesMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime) -local UpdateMessagesMenu=MENU_GROUP:New(MenuGroup,"Update Messages",MessagesMenu):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"Off",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,0):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"5 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,5):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"10 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,10):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,15):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,30):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,60):SetTime(MenuTime) -local InformationMessagesMenu=MENU_GROUP:New(MenuGroup,"Information Messages",MessagesMenu):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"5 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,5):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"10 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,10):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,15):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,30):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,60):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,120):SetTime(MenuTime) -local BriefingReportsMenu=MENU_GROUP:New(MenuGroup,"Briefing Reports",MessagesMenu):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,15):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,30):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,60):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,120):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,180):SetTime(MenuTime) -local OverviewReportsMenu=MENU_GROUP:New(MenuGroup,"Overview Reports",MessagesMenu):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,15):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,30):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,60):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,120):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,180):SetTime(MenuTime) -local DetailedReportsMenu=MENU_GROUP:New(MenuGroup,"Detailed Reports",MessagesMenu):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,15):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,30):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,60):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,120):SetTime(MenuTime) -MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,180):SetTime(MenuTime) -SettingsMenu:Remove(MenuTime) -return self -end -function SETTINGS:SetPlayerMenuOn() -self.ShowPlayerMenu=true -end -function SETTINGS:SetPlayerMenuOff() -self.ShowPlayerMenu=false -end -function SETTINGS:SetPlayerMenu(PlayerUnit) -if _SETTINGS.ShowPlayerMenu==true then -local PlayerGroup=PlayerUnit:GetGroup() -local PlayerName=PlayerUnit:GetPlayerName() -local PlayerNames=PlayerGroup:GetPlayerNames() -local PlayerMenu=MENU_GROUP:New(PlayerGroup,'Settings "'..PlayerName..'"') -self.PlayerMenu=PlayerMenu -self:T(string.format("Setting menu for player %s",tostring(PlayerName))) -local submenu=MENU_GROUP:New(PlayerGroup,"LL Accuracy",PlayerMenu) -MENU_GROUP_COMMAND:New(PlayerGroup,"LL 0 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,0) -MENU_GROUP_COMMAND:New(PlayerGroup,"LL 1 Decimal",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1) -MENU_GROUP_COMMAND:New(PlayerGroup,"LL 2 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2) -MENU_GROUP_COMMAND:New(PlayerGroup,"LL 3 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3) -MENU_GROUP_COMMAND:New(PlayerGroup,"LL 4 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,4) -local submenu=MENU_GROUP:New(PlayerGroup,"MGRS Accuracy",PlayerMenu) -MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 0",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,0) -MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 1",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1) -MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 2",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2) -MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 3",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3) -MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 4",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,4) -MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 5",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,5) -local text="A2G Coordinate System" -if _SETTINGS.MenuShort then -text="A2G Coordinates" -end -local A2GCoordinateMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu) -if not self:IsA2G_LL_DMS()or _SETTINGS.MenuStatic then -local text="Lat/Lon Degree Min Sec (LL DMS)" -if _SETTINGS.MenuShort then -text="A2G LL DMS" -end -MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DMS") -end -if not self:IsA2G_LL_DDM()or _SETTINGS.MenuStatic then -local text="Lat/Lon Degree Dec Min (LL DDM)" -if _SETTINGS.MenuShort then -text="A2G LL DDM" -end -MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DDM") -end -if not self:IsA2G_BR()or _SETTINGS.MenuStatic then -local text="Bearing, Range (BR)" -if _SETTINGS.MenuShort then -text="A2G BR" -end -MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"BR") -end -if not self:IsA2G_MGRS()or _SETTINGS.MenuStatic then -local text="Military Grid (MGRS)" -if _SETTINGS.MenuShort then -text="A2G MGRS" -end -MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"MGRS") -end -local text="A2A Coordinate System" -if _SETTINGS.MenuShort then -text="A2A Coordinates" -end -local A2ACoordinateMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu) -if not self:IsA2A_LL_DMS()or _SETTINGS.MenuStatic then -local text="Lat/Lon Degree Min Sec (LL DMS)" -if _SETTINGS.MenuShort then -text="A2A LL DMS" -end -MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DMS") -end -if not self:IsA2A_LL_DDM()or _SETTINGS.MenuStatic then -local text="Lat/Lon Degree Dec Min (LL DDM)" -if _SETTINGS.MenuShort then -text="A2A LL DDM" -end -MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DDM") -end -if not self:IsA2A_BULLS()or _SETTINGS.MenuStatic then -local text="Bullseye (BULLS)" -if _SETTINGS.MenuShort then -text="A2A BULLS" -end -MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"BULLS") -end -if not self:IsA2A_BRAA()or _SETTINGS.MenuStatic then -local text="Bearing Range Altitude Aspect (BRAA)" -if _SETTINGS.MenuShort then -text="A2A BRAA" -end -MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"BRAA") -end -if not self:IsA2A_MGRS()or _SETTINGS.MenuStatic then -local text="Military Grid (MGRS)" -if _SETTINGS.MenuShort then -text="A2A MGRS" -end -MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"MGRS") -end -local text="Measures and Weights System" -if _SETTINGS.MenuShort then -text="Unit System" -end -local MetricsMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu) -if self:IsMetric()or _SETTINGS.MenuStatic then -local text="Imperial (Miles,Feet)" -if _SETTINGS.MenuShort then -text="Imperial" -end -MENU_GROUP_COMMAND:New(PlayerGroup,text,MetricsMenu,self.MenuGroupMWSystem,self,PlayerUnit,PlayerGroup,PlayerName,false) -end -if self:IsImperial()or _SETTINGS.MenuStatic then -local text="Metric (Kilometers,Meters)" -if _SETTINGS.MenuShort then -text="Metric" -end -MENU_GROUP_COMMAND:New(PlayerGroup,text,MetricsMenu,self.MenuGroupMWSystem,self,PlayerUnit,PlayerGroup,PlayerName,true) -end -local text="Messages and Reports" -if _SETTINGS.MenuShort then -text="Messages & Reports" -end -local MessagesMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu) -local UpdateMessagesMenu=MENU_GROUP:New(PlayerGroup,"Update Messages",MessagesMenu) -MENU_GROUP_COMMAND:New(PlayerGroup,"Updates Off",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,0) -MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 5 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,5) -MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 10 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,10) -MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 15 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,15) -MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 30 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,30) -MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 1 min",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,60) -local InformationMessagesMenu=MENU_GROUP:New(PlayerGroup,"Info Messages",MessagesMenu) -MENU_GROUP_COMMAND:New(PlayerGroup,"Info 5 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,5) -MENU_GROUP_COMMAND:New(PlayerGroup,"Info 10 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,10) -MENU_GROUP_COMMAND:New(PlayerGroup,"Info 15 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,15) -MENU_GROUP_COMMAND:New(PlayerGroup,"Info 30 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,30) -MENU_GROUP_COMMAND:New(PlayerGroup,"Info 1 min",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,60) -MENU_GROUP_COMMAND:New(PlayerGroup,"Info 2 min",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,120) -local BriefingReportsMenu=MENU_GROUP:New(PlayerGroup,"Briefing Reports",MessagesMenu) -MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 15 sec",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,15) -MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 30 sec",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,30) -MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 1 min",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,60) -MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 2 min",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,120) -MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 3 min",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,180) -local OverviewReportsMenu=MENU_GROUP:New(PlayerGroup,"Overview Reports",MessagesMenu) -MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 15 sec",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,15) -MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 30 sec",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,30) -MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 1 min",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,60) -MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 2 min",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,120) -MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 3 min",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,180) -local DetailedReportsMenu=MENU_GROUP:New(PlayerGroup,"Detailed Reports",MessagesMenu) -MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 15 sec",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,15) -MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 30 sec",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,30) -MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 1 min",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,60) -MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 2 min",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,120) -MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 3 min",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,180) -end -return self -end -function SETTINGS:RemovePlayerMenu(PlayerUnit) -if self.PlayerMenu then -self.PlayerMenu:Remove() -self.PlayerMenu=nil -end -return self -end -function SETTINGS:A2GMenuSystem(MenuGroup,RootMenu,A2GSystem) -self.A2GSystem=A2GSystem -MESSAGE:New(string.format("Settings: Default A2G coordinate system set to %s for all players!",A2GSystem),5):ToAll() -self:SetSystemMenu(MenuGroup,RootMenu) -end -function SETTINGS:A2AMenuSystem(MenuGroup,RootMenu,A2ASystem) -self.A2ASystem=A2ASystem -MESSAGE:New(string.format("Settings: Default A2A coordinate system set to %s for all players!",A2ASystem),5):ToAll() -self:SetSystemMenu(MenuGroup,RootMenu) -end -function SETTINGS:MenuLL_DDM_Accuracy(MenuGroup,RootMenu,LL_Accuracy) -self.LL_Accuracy=LL_Accuracy -MESSAGE:New(string.format("Settings: Default LL accuracy set to %s for all players!",LL_Accuracy),5):ToAll() -self:SetSystemMenu(MenuGroup,RootMenu) -end -function SETTINGS:MenuMGRS_Accuracy(MenuGroup,RootMenu,MGRS_Accuracy) -self.MGRS_Accuracy=MGRS_Accuracy -MESSAGE:New(string.format("Settings: Default MGRS accuracy set to %s for all players!",MGRS_Accuracy),5):ToAll() -self:SetSystemMenu(MenuGroup,RootMenu) -end -function SETTINGS:MenuMWSystem(MenuGroup,RootMenu,MW) -self.Metric=MW -MESSAGE:New(string.format("Settings: Default measurement format set to %s for all players!",MW and"Metric"or"Imperial"),5):ToAll() -self:SetSystemMenu(MenuGroup,RootMenu) -end -function SETTINGS:MenuMessageTimingsSystem(MenuGroup,RootMenu,MessageType,MessageTime) -self:SetMessageTime(MessageType,MessageTime) -MESSAGE:New(string.format("Settings: Default message time set for %s to %d.",MessageType,MessageTime),5):ToAll() -end -do -function SETTINGS:MenuGroupA2GSystem(PlayerUnit,PlayerGroup,PlayerName,A2GSystem) -self.A2GSystem=A2GSystem -MESSAGE:New(string.format("Settings: A2G format set to %s for player %s.",A2GSystem,PlayerName),5):ToGroup(PlayerGroup) -if _SETTINGS.MenuStatic==false then -self:RemovePlayerMenu(PlayerUnit) -self:SetPlayerMenu(PlayerUnit) -end -end -function SETTINGS:MenuGroupA2ASystem(PlayerUnit,PlayerGroup,PlayerName,A2ASystem) -self.A2ASystem=A2ASystem -MESSAGE:New(string.format("Settings: A2A format set to %s for player %s.",A2ASystem,PlayerName),5):ToGroup(PlayerGroup) -if _SETTINGS.MenuStatic==false then -self:RemovePlayerMenu(PlayerUnit) -self:SetPlayerMenu(PlayerUnit) -end -end -function SETTINGS:MenuGroupLL_DDM_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,LL_Accuracy) -self.LL_Accuracy=LL_Accuracy -MESSAGE:New(string.format("Settings: LL format accuracy set to %d decimal places for player %s.",LL_Accuracy,PlayerName),5):ToGroup(PlayerGroup) -if _SETTINGS.MenuStatic==false then -self:RemovePlayerMenu(PlayerUnit) -self:SetPlayerMenu(PlayerUnit) -end -end -function SETTINGS:MenuGroupMGRS_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,MGRS_Accuracy) -self.MGRS_Accuracy=MGRS_Accuracy -MESSAGE:New(string.format("Settings: MGRS format accuracy set to %d for player %s.",MGRS_Accuracy,PlayerName),5):ToGroup(PlayerGroup) -if _SETTINGS.MenuStatic==false then -self:RemovePlayerMenu(PlayerUnit) -self:SetPlayerMenu(PlayerUnit) -end -end -function SETTINGS:MenuGroupMWSystem(PlayerUnit,PlayerGroup,PlayerName,MW) -self.Metric=MW -MESSAGE:New(string.format("Settings: Measurement format set to %s for player %s.",MW and"Metric"or"Imperial",PlayerName),5):ToGroup(PlayerGroup) -if _SETTINGS.MenuStatic==false then -self:RemovePlayerMenu(PlayerUnit) -self:SetPlayerMenu(PlayerUnit) -end -end -function SETTINGS:MenuGroupMessageTimingsSystem(PlayerUnit,PlayerGroup,PlayerName,MessageType,MessageTime) -self:SetMessageTime(MessageType,MessageTime) -MESSAGE:New(string.format("Settings: Default message time set for %s to %d.",MessageType,MessageTime),5):ToGroup(PlayerGroup) -end -end -function SETTINGS:SetEraWWII() -self.Era=SETTINGS.__Enum.Era.WWII -end -function SETTINGS:SetEraKorea() -self.Era=SETTINGS.__Enum.Era.Korea -end -function SETTINGS:SetEraCold() -self.Era=SETTINGS.__Enum.Era.Cold -end -function SETTINGS:SetEraModern() -self.Era=SETTINGS.__Enum.Era.Modern -end -end -MENU_INDEX={} -MENU_INDEX.MenuMission={} -MENU_INDEX.MenuMission.Menus={} -MENU_INDEX.Coalition={} -MENU_INDEX.Coalition[coalition.side.BLUE]={} -MENU_INDEX.Coalition[coalition.side.BLUE].Menus={} -MENU_INDEX.Coalition[coalition.side.RED]={} -MENU_INDEX.Coalition[coalition.side.RED].Menus={} -MENU_INDEX.Group={} -function MENU_INDEX:ParentPath(ParentMenu,MenuText) -local Path=ParentMenu and"@"..table.concat(ParentMenu.MenuPath or{},"@")or"" -if ParentMenu then -if ParentMenu:IsInstanceOf("MENU_GROUP")or ParentMenu:IsInstanceOf("MENU_GROUP_COMMAND")then -local GroupName=ParentMenu.Group:GetName() -if not self.Group[GroupName].Menus[Path]then -BASE:E({Path=Path,GroupName=GroupName}) -error("Parent path not found in menu index for group menu") -return nil -end -elseif ParentMenu:IsInstanceOf("MENU_COALITION")or ParentMenu:IsInstanceOf("MENU_COALITION_COMMAND")then -local Coalition=ParentMenu.Coalition -if not self.Coalition[Coalition].Menus[Path]then -BASE:E({Path=Path,Coalition=Coalition}) -error("Parent path not found in menu index for coalition menu") -return nil -end -elseif ParentMenu:IsInstanceOf("MENU_MISSION")or ParentMenu:IsInstanceOf("MENU_MISSION_COMMAND")then -if not self.MenuMission.Menus[Path]then -BASE:E({Path=Path}) -error("Parent path not found in menu index for mission menu") -return nil -end -end -end -Path=Path.."@"..MenuText -return Path -end -function MENU_INDEX:PrepareMission() -self.MenuMission.Menus=self.MenuMission.Menus or{} -end -function MENU_INDEX:PrepareCoalition(CoalitionSide) -self.Coalition[CoalitionSide]=self.Coalition[CoalitionSide]or{} -self.Coalition[CoalitionSide].Menus=self.Coalition[CoalitionSide].Menus or{} -end -function MENU_INDEX:PrepareGroup(Group) -if Group and Group:IsAlive()~=nil then -local GroupName=Group:GetName() -self.Group[GroupName]=self.Group[GroupName]or{} -self.Group[GroupName].Menus=self.Group[GroupName].Menus or{} -end -end -function MENU_INDEX:HasMissionMenu(Path) -return self.MenuMission.Menus[Path] -end -function MENU_INDEX:SetMissionMenu(Path,Menu) -self.MenuMission.Menus[Path]=Menu -end -function MENU_INDEX:ClearMissionMenu(Path) -self.MenuMission.Menus[Path]=nil -end -function MENU_INDEX:HasCoalitionMenu(Coalition,Path) -return self.Coalition[Coalition].Menus[Path] -end -function MENU_INDEX:SetCoalitionMenu(Coalition,Path,Menu) -self.Coalition[Coalition].Menus[Path]=Menu -end -function MENU_INDEX:ClearCoalitionMenu(Coalition,Path) -self.Coalition[Coalition].Menus[Path]=nil -end -function MENU_INDEX:HasGroupMenu(Group,Path) -if Group and Group:IsAlive()then -local MenuGroupName=Group:GetName() -return self.Group[MenuGroupName].Menus[Path] -end -return nil -end -function MENU_INDEX:SetGroupMenu(Group,Path,Menu) -local MenuGroupName=Group:GetName() -Group:F({MenuGroupName=MenuGroupName,Path=Path}) -self.Group[MenuGroupName].Menus[Path]=Menu -end -function MENU_INDEX:ClearGroupMenu(Group,Path) -local MenuGroupName=Group:GetName() -self.Group[MenuGroupName].Menus[Path]=nil -end -function MENU_INDEX:Refresh(Group) -for MenuID,Menu in pairs(self.MenuMission.Menus)do -Menu:Refresh() -end -for MenuID,Menu in pairs(self.Coalition[coalition.side.BLUE].Menus)do -Menu:Refresh() -end -for MenuID,Menu in pairs(self.Coalition[coalition.side.RED].Menus)do -Menu:Refresh() -end -local GroupName=Group:GetName() -for MenuID,Menu in pairs(self.Group[GroupName].Menus)do -Menu:Refresh() -end -return self -end -do -MENU_BASE={ -ClassName="MENU_BASE", -MenuPath=nil, -MenuText="", -MenuParentPath=nil, -} -function MENU_BASE:New(MenuText,ParentMenu) -local MenuParentPath={} -if ParentMenu~=nil then -MenuParentPath=ParentMenu.MenuPath -end -local self=BASE:Inherit(self,BASE:New()) -self.MenuPath=nil -self.MenuText=MenuText -self.ParentMenu=ParentMenu -self.MenuParentPath=MenuParentPath -self.Path=(self.ParentMenu and"@"..table.concat(self.MenuParentPath or{},"@")or"").."@"..self.MenuText -self.Menus={} -self.MenuCount=0 -self.MenuStamp=timer.getTime() -self.MenuRemoveParent=false -if self.ParentMenu then -self.ParentMenu.Menus=self.ParentMenu.Menus or{} -self.ParentMenu.Menus[MenuText]=self -end -return self -end -function MENU_BASE:SetParentMenu(MenuText,Menu) -if self.ParentMenu then -self.ParentMenu.Menus=self.ParentMenu.Menus or{} -self.ParentMenu.Menus[MenuText]=Menu -self.ParentMenu.MenuCount=self.ParentMenu.MenuCount+1 -end -end -function MENU_BASE:ClearParentMenu(MenuText) -if self.ParentMenu and self.ParentMenu.Menus[MenuText]then -self.ParentMenu.Menus[MenuText]=nil -self.ParentMenu.MenuCount=self.ParentMenu.MenuCount-1 -if self.ParentMenu.MenuCount==0 then -end -end -end -function MENU_BASE:SetRemoveParent(RemoveParent) -self.MenuRemoveParent=RemoveParent -return self -end -function MENU_BASE:GetMenu(MenuText) -return self.Menus[MenuText] -end -function MENU_BASE:SetStamp(MenuStamp) -self.MenuStamp=MenuStamp -return self -end -function MENU_BASE:GetStamp() -return timer.getTime() -end -function MENU_BASE:SetTime(MenuStamp) -self.MenuStamp=MenuStamp -return self -end -function MENU_BASE:SetTag(MenuTag) -self.MenuTag=MenuTag -return self -end -end -do -MENU_COMMAND_BASE={ -ClassName="MENU_COMMAND_BASE", -CommandMenuFunction=nil, -CommandMenuArgument=nil, -MenuCallHandler=nil, -} -function MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,CommandMenuArguments) -local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu)) -local ErrorHandler=function(errmsg) -env.info("MOOSE error in MENU COMMAND function: "..errmsg) -if BASE.Debug~=nil then -env.info(BASE.Debug.traceback()) -end -return errmsg -end -self:SetCommandMenuFunction(CommandMenuFunction) -self:SetCommandMenuArguments(CommandMenuArguments) -self.MenuCallHandler=function() -local function MenuFunction() -return self.CommandMenuFunction(unpack(self.CommandMenuArguments)) -end -local Status,Result=xpcall(MenuFunction,ErrorHandler) -end -return self -end -function MENU_COMMAND_BASE:SetCommandMenuFunction(CommandMenuFunction) -self.CommandMenuFunction=CommandMenuFunction -return self -end -function MENU_COMMAND_BASE:SetCommandMenuArguments(CommandMenuArguments) -self.CommandMenuArguments=CommandMenuArguments -return self -end -end -do -MENU_MISSION={ -ClassName="MENU_MISSION", -} -function MENU_MISSION:New(MenuText,ParentMenu) -MENU_INDEX:PrepareMission() -local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText) -local MissionMenu=MENU_INDEX:HasMissionMenu(Path) -if MissionMenu then -return MissionMenu -else -local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu)) -MENU_INDEX:SetMissionMenu(Path,self) -self.MenuPath=missionCommands.addSubMenu(self.MenuText,self.MenuParentPath) -self:SetParentMenu(self.MenuText,self) -return self -end -end -function MENU_MISSION:Refresh() -do -missionCommands.removeItem(self.MenuPath) -self.MenuPath=missionCommands.addSubMenu(self.MenuText,self.MenuParentPath) -end -return self -end -function MENU_MISSION:RemoveSubMenus() -for MenuID,Menu in pairs(self.Menus or{})do -Menu:Remove() -end -self.Menus=nil -end -function MENU_MISSION:Remove(MenuStamp,MenuTag) -MENU_INDEX:PrepareMission() -local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText) -local MissionMenu=MENU_INDEX:HasMissionMenu(Path) -if MissionMenu==self then -self:RemoveSubMenus() -if not MenuStamp or self.MenuStamp~=MenuStamp then -if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then -self:F({Text=self.MenuText,Path=self.MenuPath}) -if self.MenuPath~=nil then -missionCommands.removeItem(self.MenuPath) -end -MENU_INDEX:ClearMissionMenu(self.Path) -self:ClearParentMenu(self.MenuText) -return nil -end -end -else -BASE:E({"Cannot Remove MENU_MISSION",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText}) -end -return self -end -end -do -MENU_MISSION_COMMAND={ -ClassName="MENU_MISSION_COMMAND", -} -function MENU_MISSION_COMMAND:New(MenuText,ParentMenu,CommandMenuFunction,...) -MENU_INDEX:PrepareMission() -local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText) -local MissionMenu=MENU_INDEX:HasMissionMenu(Path) -if MissionMenu then -MissionMenu:SetCommandMenuFunction(CommandMenuFunction) -MissionMenu:SetCommandMenuArguments(arg) -return MissionMenu -else -local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg)) -MENU_INDEX:SetMissionMenu(Path,self) -self.MenuPath=missionCommands.addCommand(MenuText,self.MenuParentPath,self.MenuCallHandler) -self:SetParentMenu(self.MenuText,self) -return self -end -end -function MENU_MISSION_COMMAND:Refresh() -do -missionCommands.removeItem(self.MenuPath) -missionCommands.addCommand(self.MenuText,self.MenuParentPath,self.MenuCallHandler) -end -return self -end -function MENU_MISSION_COMMAND:Remove() -MENU_INDEX:PrepareMission() -local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText) -local MissionMenu=MENU_INDEX:HasMissionMenu(Path) -if MissionMenu==self then -if not MenuStamp or self.MenuStamp~=MenuStamp then -if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then -self:F({Text=self.MenuText,Path=self.MenuPath}) -if self.MenuPath~=nil then -missionCommands.removeItem(self.MenuPath) -end -MENU_INDEX:ClearMissionMenu(self.Path) -self:ClearParentMenu(self.MenuText) -return nil -end -end -else -BASE:E({"Cannot Remove MENU_MISSION_COMMAND",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText}) -end -return self -end -end -do -MENU_COALITION={ -ClassName="MENU_COALITION" -} -function MENU_COALITION:New(Coalition,MenuText,ParentMenu) -MENU_INDEX:PrepareCoalition(Coalition) -local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText) -local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(Coalition,Path) -if CoalitionMenu then -return CoalitionMenu -else -local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu)) -MENU_INDEX:SetCoalitionMenu(Coalition,Path,self) -self.Coalition=Coalition -self.MenuPath=missionCommands.addSubMenuForCoalition(Coalition,MenuText,self.MenuParentPath) -self:SetParentMenu(self.MenuText,self) -return self -end -end -function MENU_COALITION:Refresh() -do -missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath) -missionCommands.addSubMenuForCoalition(self.Coalition,self.MenuText,self.MenuParentPath) -end -return self -end -function MENU_COALITION:RemoveSubMenus() -for MenuID,Menu in pairs(self.Menus or{})do -Menu:Remove() -end -self.Menus=nil -end -function MENU_COALITION:Remove(MenuStamp,MenuTag) -MENU_INDEX:PrepareCoalition(self.Coalition) -local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText) -local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(self.Coalition,Path) -if CoalitionMenu==self then -self:RemoveSubMenus() -if not MenuStamp or self.MenuStamp~=MenuStamp then -if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then -self:F({Coalition=self.Coalition,Text=self.MenuText,Path=self.MenuPath}) -if self.MenuPath~=nil then -missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath) -end -MENU_INDEX:ClearCoalitionMenu(self.Coalition,Path) -self:ClearParentMenu(self.MenuText) -return nil -end -end -else -BASE:E({"Cannot Remove MENU_COALITION",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Coalition=self.Coalition}) -end -return self -end -end -do -MENU_COALITION_COMMAND={ -ClassName="MENU_COALITION_COMMAND" -} -function MENU_COALITION_COMMAND:New(Coalition,MenuText,ParentMenu,CommandMenuFunction,...) -MENU_INDEX:PrepareCoalition(Coalition) -local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText) -local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(Coalition,Path) -if CoalitionMenu then -CoalitionMenu:SetCommandMenuFunction(CommandMenuFunction) -CoalitionMenu:SetCommandMenuArguments(arg) -return CoalitionMenu -else -local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg)) -MENU_INDEX:SetCoalitionMenu(Coalition,Path,self) -self.Coalition=Coalition -self.MenuPath=missionCommands.addCommandForCoalition(self.Coalition,MenuText,self.MenuParentPath,self.MenuCallHandler) -self:SetParentMenu(self.MenuText,self) -return self -end -end -function MENU_COALITION_COMMAND:Refresh() -do -missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath) -missionCommands.addCommandForCoalition(self.Coalition,self.MenuText,self.MenuParentPath,self.MenuCallHandler) -end -return self -end -function MENU_COALITION_COMMAND:Remove(MenuStamp,MenuTag) -MENU_INDEX:PrepareCoalition(self.Coalition) -local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText) -local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(self.Coalition,Path) -if CoalitionMenu==self then -if not MenuStamp or self.MenuStamp~=MenuStamp then -if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then -self:F({Coalition=self.Coalition,Text=self.MenuText,Path=self.MenuPath}) -if self.MenuPath~=nil then -missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath) -end -MENU_INDEX:ClearCoalitionMenu(self.Coalition,Path) -self:ClearParentMenu(self.MenuText) -return nil -end -end -else -BASE:E({"Cannot Remove MENU_COALITION_COMMAND",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Coalition=self.Coalition}) -end -return self -end -end -do -local _MENUGROUPS={} -MENU_GROUP={ -ClassName="MENU_GROUP" -} -function MENU_GROUP:New(Group,MenuText,ParentMenu) -MENU_INDEX:PrepareGroup(Group) -local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText) -local GroupMenu=MENU_INDEX:HasGroupMenu(Group,Path) -if GroupMenu then -return GroupMenu -else -self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu)) -MENU_INDEX:SetGroupMenu(Group,Path,self) -self.Group=Group -self.GroupID=Group:GetID() -self.MenuPath=missionCommands.addSubMenuForGroup(self.GroupID,MenuText,self.MenuParentPath) -self:SetParentMenu(self.MenuText,self) -return self -end -end -function MENU_GROUP:Refresh() -do -missionCommands.removeItemForGroup(self.GroupID,self.MenuPath) -missionCommands.addSubMenuForGroup(self.GroupID,self.MenuText,self.MenuParentPath) -for MenuText,Menu in pairs(self.Menus or{})do -Menu:Refresh() -end -end -return self -end -function MENU_GROUP:RefreshAndOrderByTag() -do -missionCommands.removeItemForGroup(self.GroupID,self.MenuPath) -missionCommands.addSubMenuForGroup(self.GroupID,self.MenuText,self.MenuParentPath) -local MenuTable={} -for MenuText,Menu in pairs(self.Menus or{})do -local tag=Menu.MenuTag or math.random(1,10000) -MenuTable[#MenuTable+1]={Tag=tag,Enty=Menu} -end -table.sort(MenuTable,function(k1,k2)return k1.tag0 then -self:ScheduleOnce(Delay,ZONE_BASE.UndrawZone,self) -else -if self.DrawID then -if type(self.DrawID)~="table"then -UTILS.RemoveMark(self.DrawID) -else -for _,mark_id in pairs(self.DrawID)do -UTILS.RemoveMark(mark_id) -end -end -end -end -return self -end -function ZONE_BASE:GetDrawID() -return self.DrawID -end -function ZONE_BASE:SmokeZone(SmokeColor) -self:F2(SmokeColor) -end -function ZONE_BASE:SetZoneProbability(ZoneProbability) -self:F({self:GetName(),ZoneProbability=ZoneProbability}) -self.ZoneProbability=ZoneProbability or 1 -return self -end -function ZONE_BASE:GetZoneProbability() -self:F2() -return self.ZoneProbability -end -function ZONE_BASE:GetZoneMaybe() -self:F2() -local Randomization=math.random() -if Randomization<=self.ZoneProbability then -return self -else -return nil -end -end -function ZONE_BASE:SetCheckTime(seconds) -self.Checktime=seconds or 5 -return self -end -function ZONE_BASE:Trigger(Objects) -self:SetStartState("TriggerStopped") -self:AddTransition("TriggerStopped","TriggerStart","TriggerRunning") -self:AddTransition("*","EnteredZone","*") -self:AddTransition("*","LeftZone","*") -self:AddTransition("*","TriggerRunCheck","*") -self:AddTransition("*","TriggerStop","TriggerStopped") -self:TriggerStart() -self.checkobjects=Objects -if UTILS.IsInstanceOf(Objects,"SET_BASE")then -self.objectset=Objects.Set -else -self.objectset={Objects} -end -self:_TriggerCheck(true) -self:__TriggerRunCheck(self.Checktime) -return self -end -function ZONE_BASE:_TriggerCheck(fromstart) -local objectset=self.objectset or{} -if fromstart then -for _,_object in pairs(objectset)do -local obj=_object -if not obj.TriggerInZone then obj.TriggerInZone={}end -if obj and obj:IsAlive()and self:IsCoordinateInZone(obj:GetCoordinate())then -obj.TriggerInZone[self.ZoneName]=true -else -obj.TriggerInZone[self.ZoneName]=false -end -end -else -for _,_object in pairs(objectset)do -local obj=_object -if obj and obj:IsAlive()then -if not obj.TriggerInZone then -obj.TriggerInZone={} -end -if not obj.TriggerInZone[self.ZoneName]then -obj.TriggerInZone[self.ZoneName]=false -end -local inzone=self:IsCoordinateInZone(obj:GetCoordinate()) -if inzone and not obj.TriggerInZone[self.ZoneName]then -self:__EnteredZone(0.5,obj) -obj.TriggerInZone[self.ZoneName]=true -elseif(not inzone)and obj.TriggerInZone[self.ZoneName]then -self:__LeftZone(0.5,obj) -obj.TriggerInZone[self.ZoneName]=false -else -end -end -end -end -return self -end -function ZONE_BASE:onafterTriggerRunCheck(From,Event,To) -if self:GetState()~="TriggerStopped"then -self:_TriggerCheck() -self:__TriggerRunCheck(self.Checktime) -end -return self -end -function ZONE_BASE:GetProperty(PropertyName) -return self.Properties[PropertyName] -end -function ZONE_BASE:GetAllProperties() -return self.Properties -end -ZONE_RADIUS={ -ClassName="ZONE_RADIUS", -} -function ZONE_RADIUS:New(ZoneName,Vec2,Radius,DoNotRegisterZone) -local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName)) -self:F({ZoneName,Vec2,Radius}) -self.Radius=Radius -self.Vec2=Vec2 -if not DoNotRegisterZone then -_EVENTDISPATCHER:CreateEventNewZone(self) -end -return self -end -function ZONE_RADIUS:UpdateFromVec2(Vec2,Radius) -self.Vec2=Vec2 -if Radius then -self.Radius=Radius -end -return self -end -function ZONE_RADIUS:UpdateFromVec3(Vec3,Radius) -self.Vec2.x=Vec3.x -self.Vec2.y=Vec3.z -if Radius then -self.Radius=Radius -end -return self -end -function ZONE_RADIUS:MarkZone(Points) -local Point={} -local Vec2=self:GetVec2() -Points=Points and Points or 360 -local Angle -local RadialBase=math.pi*2 -for Angle=0,360,(360/Points)do -local Radial=Angle*RadialBase/360 -Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() -Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() -COORDINATE:NewFromVec2(Point):MarkToAll(self:GetName()) -end -end -function ZONE_RADIUS:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly) -local coordinate=self:GetCoordinate() -local Radius=self:GetRadius() -Color=Color or self:GetColorRGB() -Alpha=Alpha or 1 -FillColor=FillColor or UTILS.DeepCopy(Color) -FillAlpha=FillAlpha or self:GetColorAlpha() -self.DrawID=coordinate:CircleToAll(Radius,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly) -return self -end -function ZONE_RADIUS:BoundZone(Points,CountryID,UnBound) -local Point={} -local Vec2=self:GetVec2() -Points=Points and Points or 360 -local Angle -local RadialBase=math.pi*2 -for Angle=0,360,(360/Points)do -local Radial=Angle*RadialBase/360 -Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() -Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() -local CountryName=_DATABASE.COUNTRY_NAME[CountryID] -local Tire={ -["country"]=CountryName, -["category"]="Fortifications", -["canCargo"]=false, -["shape_name"]="H-tyre_B_WF", -["type"]="Black_Tyre_WF", -["y"]=Point.y, -["x"]=Point.x, -["name"]=string.format("%s-Tire #%0d",self:GetName(),Angle), -["heading"]=0, -} -local Group=coalition.addStaticObject(CountryID,Tire) -if UnBound and UnBound==true then -Group:destroy() -end -end -return self -end -function ZONE_RADIUS:SmokeZone(SmokeColor,Points,AddHeight,AngleOffset) -self:F2(SmokeColor) -local Point={} -local Vec2=self:GetVec2() -AddHeight=AddHeight or 0 -AngleOffset=AngleOffset or 0 -Points=Points and Points or 360 -local Angle -local RadialBase=math.pi*2 -for Angle=0,360,360/Points do -local Radial=(Angle+AngleOffset)*RadialBase/360 -Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() -Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() -POINT_VEC2:New(Point.x,Point.y,AddHeight):Smoke(SmokeColor) -end -return self -end -function ZONE_RADIUS:FlareZone(FlareColor,Points,Azimuth,AddHeight) -self:F2({FlareColor,Azimuth}) -local Point={} -local Vec2=self:GetVec2() -AddHeight=AddHeight or 0 -Points=Points and Points or 360 -local Angle -local RadialBase=math.pi*2 -for Angle=0,360,360/Points do -local Radial=Angle*RadialBase/360 -Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() -Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() -POINT_VEC2:New(Point.x,Point.y,AddHeight):Flare(FlareColor,Azimuth) -end -return self -end -function ZONE_RADIUS:GetRadius() -self:F2(self.ZoneName) -self:T2({self.Radius}) -return self.Radius -end -function ZONE_RADIUS:SetRadius(Radius) -self:F2(self.ZoneName) -self.Radius=Radius -self:T2({self.Radius}) -return self.Radius -end -function ZONE_RADIUS:GetVec2() -self:F2(self.ZoneName) -self:T2({self.Vec2}) -return self.Vec2 -end -function ZONE_RADIUS:SetVec2(Vec2) -self:F2(self.ZoneName) -self.Vec2=Vec2 -self:T2({self.Vec2}) -return self.Vec2 -end -function ZONE_RADIUS:GetVec3(Height) -self:F2({self.ZoneName,Height}) -Height=Height or 0 -local Vec2=self:GetVec2() -local Vec3={x=Vec2.x,y=land.getHeight(self:GetVec2())+Height,z=Vec2.y} -self:T2({Vec3}) -return Vec3 -end -function ZONE_RADIUS:Scan(ObjectCategories,UnitCategories) -self.ScanData={} -self.ScanData.Coalitions={} -self.ScanData.Scenery={} -self.ScanData.SceneryTable={} -self.ScanData.Units={} -local ZoneCoord=self:GetCoordinate() -local ZoneRadius=self:GetRadius() -local SphereSearch={ -id=world.VolumeType.SPHERE, -params={ -point=ZoneCoord:GetVec3(), -radius=ZoneRadius, -} -} -local function EvaluateZone(ZoneObject) -if ZoneObject then -local ObjectCategory=Object.getCategory(ZoneObject) -if(ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()and ZoneObject:isActive())or(ObjectCategory==Object.Category.STATIC and ZoneObject:isExist())then -local CoalitionDCSUnit=ZoneObject:getCoalition() -local Include=false -if not UnitCategories then -Include=true -else -local CategoryDCSUnit=ZoneObject:getDesc().category -for UnitCategoryID,UnitCategory in pairs(UnitCategories)do -if UnitCategory==CategoryDCSUnit then -Include=true -break -end -end -end -if Include then -local CoalitionDCSUnit=ZoneObject:getCoalition() -self.ScanData.Coalitions[CoalitionDCSUnit]=true -self.ScanData.Units[ZoneObject]=ZoneObject -self:F2({Name=ZoneObject:getName(),Coalition=CoalitionDCSUnit}) -end -end -if ObjectCategory==Object.Category.SCENERY then -local SceneryType=ZoneObject:getTypeName() -local SceneryName=ZoneObject:getName() -self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{} -self.ScanData.Scenery[SceneryType][SceneryName]=SCENERY:Register(tostring(SceneryName),ZoneObject) -table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName]) -self:T({SCENERY=self.ScanData.Scenery[SceneryType][SceneryName]}) -end -end -return true -end -world.searchObjects(ObjectCategories,SphereSearch,EvaluateZone) -end -function ZONE_RADIUS:RemoveJunk() -local radius=self.Radius -local vec3=self:GetVec3() -local volS={ -id=world.VolumeType.SPHERE, -params={point=vec3,radius=radius} -} -local n=world.removeJunk(volS) -return n -end -function ZONE_RADIUS:GetScannedUnits() -return self.ScanData.Units -end -function ZONE_RADIUS:GetScannedSetUnit() -local SetUnit=SET_UNIT:New() -if self.ScanData then -for ObjectID,UnitObject in pairs(self.ScanData.Units)do -local UnitObject=UnitObject -if UnitObject:isExist()then -local FoundUnit=UNIT:FindByName(UnitObject:getName()) -if FoundUnit then -SetUnit:AddUnit(FoundUnit) -else -local FoundStatic=STATIC:FindByName(UnitObject:getName()) -if FoundStatic then -SetUnit:AddUnit(FoundStatic) -end -end -end -end -end -return SetUnit -end -function ZONE_RADIUS:GetScannedSetGroup() -self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() -self.ScanSetGroup.Set={} -if self.ScanData then -for ObjectID,UnitObject in pairs(self.ScanData.Units)do -local UnitObject=UnitObject -if UnitObject:isExist()then -local FoundUnit=UNIT:FindByName(UnitObject:getName()) -if FoundUnit then -local group=FoundUnit:GetGroup() -self.ScanSetGroup:AddGroup(group) -end -end -end -end -return self.ScanSetGroup -end -function ZONE_RADIUS:CountScannedCoalitions() -local Count=0 -for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do -Count=Count+1 -end -return Count -end -function ZONE_RADIUS:CheckScannedCoalition(Coalition) -if Coalition then -return self.ScanData.Coalitions[Coalition] -end -return nil -end -function ZONE_RADIUS:GetScannedCoalition(Coalition) -if Coalition then -return self.ScanData.Coalitions[Coalition] -else -local Count=0 -local ReturnCoalition=nil -for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do -Count=Count+1 -ReturnCoalition=CoalitionID -end -if Count~=1 then -ReturnCoalition=nil -end -return ReturnCoalition -end -end -function ZONE_RADIUS:GetScannedSceneryType(SceneryType) -return self.ScanData.Scenery[SceneryType] -end -function ZONE_RADIUS:GetScannedScenery() -return self.ScanData.Scenery -end -function ZONE_RADIUS:GetScannedSceneryObjects() -return self.ScanData.SceneryTable -end -function ZONE_RADIUS:GetScannedSetScenery() -local scenery=SET_SCENERY:New() -local objects=self:GetScannedSceneryObjects() -for _,_obj in pairs(objects)do -scenery:AddScenery(_obj) -end -return scenery -end -function ZONE_RADIUS:IsAllInZoneOfCoalition(Coalition) -return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==true -end -function ZONE_RADIUS:IsAllInZoneOfOtherCoalition(Coalition) -return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==nil -end -function ZONE_RADIUS:IsSomeInZoneOfCoalition(Coalition) -return self:CountScannedCoalitions()>1 and self:GetScannedCoalition(Coalition)==true -end -function ZONE_RADIUS:IsNoneInZoneOfCoalition(Coalition) -return self:GetScannedCoalition(Coalition)==nil -end -function ZONE_RADIUS:IsNoneInZone() -return self:CountScannedCoalitions()==0 -end -function ZONE_RADIUS:SearchZone(EvaluateFunction,ObjectCategories) -local SearchZoneResult=true -local ZoneCoord=self:GetCoordinate() -local ZoneRadius=self:GetRadius() -self:F({ZoneCoord=ZoneCoord,ZoneRadius=ZoneRadius,ZoneCoordLL=ZoneCoord:ToStringLLDMS()}) -local SphereSearch={ -id=world.VolumeType.SPHERE, -params={ -point=ZoneCoord:GetVec3(), -radius=ZoneRadius/2, -} -} -local function EvaluateZone(ZoneDCSUnit) -local ZoneUnit=UNIT:Find(ZoneDCSUnit) -return EvaluateFunction(ZoneUnit) -end -world.searchObjects(Object.Category.UNIT,SphereSearch,EvaluateZone) -end -function ZONE_RADIUS:IsVec2InZone(Vec2) -self:F2(Vec2) -if not Vec2 then return false end -local ZoneVec2=self:GetVec2() -if ZoneVec2 then -if((Vec2.x-ZoneVec2.x)^2+(Vec2.y-ZoneVec2.y)^2)^0.5<=self:GetRadius()then -return true -end -end -return false -end -function ZONE_RADIUS:IsVec3InZone(Vec3) -self:F2(Vec3) -if not Vec3 then return false end -local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z}) -return InZone -end -function ZONE_RADIUS:GetRandomVec2(inner,outer,surfacetypes) -local Vec2=self:GetVec2() -local _inner=inner or 0 -local _outer=outer or self:GetRadius() -if surfacetypes and type(surfacetypes)~="table"then -surfacetypes={surfacetypes} -end -local function _getpoint() -local point={} -local angle=math.random()*math.pi*2 -point.x=Vec2.x+math.cos(angle)*math.random(_inner,_outer) -point.y=Vec2.y+math.sin(angle)*math.random(_inner,_outer) -return point -end -local function _checkSurface(point) -local stype=land.getSurfaceType(point) -for _,sf in pairs(surfacetypes)do -if sf==stype then -return true -end -end -return false -end -local point=_getpoint() -if surfacetypes then -local N=1;local Nmax=100;local gotit=false -while gotit==false and N<=Nmax do -gotit=_checkSurface(point) -if gotit then -else -point=_getpoint() -N=N+1 -end -end -end -return point -end -function ZONE_RADIUS:GetRandomPointVec2(inner,outer) -self:F(self.ZoneName,inner,outer) -local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2(inner,outer)) -self:T3({PointVec2}) -return PointVec2 -end -function ZONE_RADIUS:GetRandomVec3(inner,outer) -self:F(self.ZoneName,inner,outer) -local Vec2=self:GetRandomVec2(inner,outer) -self:T3({x=Vec2.x,y=self.y,z=Vec2.y}) -return{x=Vec2.x,y=self.y,z=Vec2.y} -end -function ZONE_RADIUS:GetRandomPointVec3(inner,outer) -self:F(self.ZoneName,inner,outer) -local PointVec3=POINT_VEC3:NewFromVec2(self:GetRandomVec2(inner,outer)) -self:T3({PointVec3}) -return PointVec3 -end -function ZONE_RADIUS:GetRandomCoordinate(inner,outer,surfacetypes) -local vec2=self:GetRandomVec2(inner,outer,surfacetypes) -local Coordinate=COORDINATE:NewFromVec2(vec2) -return Coordinate -end -function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,markbuildings,markfinal) -local dist=distance or 100 -local objects={} -if self.ScanData and self.ScanData.Scenery then -objects=self:GetScannedScenery() -else -self:Scan({Object.Category.SCENERY}) -objects=self:GetScannedScenery() -end -local T0=timer.getTime() -local T1=timer.getTime() -local buildings={} -local buildingzones={} -if self.ScanData and self.ScanData.BuildingCoordinates then -buildings=self.ScanData.BuildingCoordinates -buildingzones=self.ScanData.BuildingZones -else -for _,_object in pairs(objects)do -for _,_scen in pairs(_object)do -local scenery=_scen -local description=scenery:GetDesc() -if description and description.attributes and description.attributes.Buildings then -if markbuildings then -MARKER:New(scenery:GetCoordinate(),"Building"):ToAll() -end -buildings[#buildings+1]=scenery:GetCoordinate() -local bradius=scenery:GetBoundingRadius()or dist -local bzone=ZONE_RADIUS:New("Building-"..math.random(1,100000),scenery:GetVec2(),bradius,false) -buildingzones[#buildingzones+1]=bzone -end -end -end -self.ScanData.BuildingCoordinates=buildings -self.ScanData.BuildingZones=buildingzones -end -local rcoord=nil -local found=true -local iterations=0 -for i=1,1000 do -iterations=iterations+1 -rcoord=self:GetRandomCoordinate(inner,outer) -found=true -for _,_coord in pairs(buildingzones)do -local zone=_coord -if zone:IsPointVec2InZone(rcoord)then -found=false -break -end -end -if found then -if markfinal then -MARKER:New(rcoord,"FREE"):ToAll() -end -break -end -end -if not found then -local rcoord=nil -local found=true -local iterations=0 -for i=1,1000 do -iterations=iterations+1 -rcoord=self:GetRandomCoordinate(inner,outer) -found=true -for _,_coord in pairs(buildings)do -local coord=_coord -if coord:Get3DDistance(rcoord)0)or(d2>0)or(d3>0) -return not(has_neg and has_pos) -end -function _ZONE_TRIANGLE:GetRandomVec2(points) -points=points or self.Points -local pt={math.random(),math.random()} -table.sort(pt) -local s=pt[1] -local t=pt[2]-pt[1] -local u=1-pt[2] -return{x=s*points[1].x+t*points[2].x+u*points[3].x, -y=s*points[1].y+t*points[2].y+u*points[3].y} -end -function _ZONE_TRIANGLE:Draw(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly) -Coalition=Coalition or-1 -Color=Color or{1,0,0} -Alpha=Alpha or 1 -FillColor=FillColor or Color -if not FillColor then UTILS.DeepCopy(Color)end -FillAlpha=FillAlpha or Alpha -if not FillAlpha then FillAlpha=1 end -for i=1,#self.Coords do -local c1=self.Coords[i] -local c2=self.Coords[i%#self.Coords+1] -local id=c1:LineToAll(c2,Coalition,Color,Alpha,LineType,ReadOnly) -self.DrawID[#self.DrawID+1]=id -end -local newID=self.Coords[1]:MarkupToAllFreeForm({self.Coords[2],self.Coords[3]},Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly) -self.DrawID[#self.DrawID+1]=newID -return self.DrawID -end -function _ZONE_TRIANGLE:Fill(Coalition,FillColor,FillAlpha,ReadOnly) -Coalition=Coalition or-1 -FillColor=FillColor -FillAlpha=FillAlpha -local newID=self.Coords[1]:MarkupToAllFreeForm({self.Coords[2],self.Coords[3]},Coalition,nil,nil,FillColor,FillAlpha,0,nil) -self.DrawID[#self.DrawID+1]=newID -return self.DrawID -end -ZONE_POLYGON_BASE={ -ClassName="ZONE_POLYGON_BASE", -_Triangles={}, -SurfaceArea=0, -DrawID={}, -FillTriangles={}, -Borderlines={}, -} -function ZONE_POLYGON_BASE:New(ZoneName,PointsArray) -local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName)) -self:F({ZoneName,PointsArray}) -if PointsArray then -self._.Polygon={} -for i=1,#PointsArray do -self._.Polygon[i]={} -self._.Polygon[i].x=PointsArray[i].x -self._.Polygon[i].y=PointsArray[i].y -end -self._Triangles=self:_Triangulate() -self.SurfaceArea=self:_CalculateSurfaceArea() -end -return self -end -function ZONE_POLYGON_BASE:_Triangulate() -local points=self._.Polygon -local triangles={} -local function get_orientation(shape_points) -local sum=0 -for i=1,#shape_points do -local j=i%#shape_points+1 -sum=sum+(shape_points[j].x-shape_points[i].x)*(shape_points[j].y+shape_points[i].y) -end -return sum>=0 and"clockwise"or"counter-clockwise" -end -local function ensure_clockwise(shape_points) -local orientation=get_orientation(shape_points) -if orientation=="counter-clockwise"then -local reversed={} -for i=#shape_points,1,-1 do -table.insert(reversed,shape_points[i]) -end -return reversed -end -return shape_points -end -local function is_clockwise(p1,p2,p3) -local cross_product=(p2.x-p1.x)*(p3.y-p1.y)-(p2.y-p1.y)*(p3.x-p1.x) -return cross_product<0 -end -local function divide_recursively(shape_points) -if#shape_points==3 then -table.insert(triangles,_ZONE_TRIANGLE:New(shape_points[1],shape_points[2],shape_points[3])) -elseif#shape_points>3 then -for i,p1 in ipairs(shape_points)do -local p2=shape_points[(i%#shape_points)+1] -local p3=shape_points[(i+1)%#shape_points+1] -local triangle=_ZONE_TRIANGLE:New(p1,p2,p3) -local is_ear=true -if not is_clockwise(p1,p2,p3)then -is_ear=false -else -for _,point in ipairs(shape_points)do -if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then -is_ear=false -break -end -end -end -if is_ear then -local is_valid_triangle=true -for _,point in ipairs(points)do -if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then -is_valid_triangle=false -break -end -end -if is_valid_triangle then -table.insert(triangles,triangle) -local remaining_points={} -for j,point in ipairs(shape_points)do -if point~=p2 then -table.insert(remaining_points,point) -end -end -divide_recursively(remaining_points) -break -end -else -end -end -end -end -points=ensure_clockwise(points) -divide_recursively(points) -return triangles -end -function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array) -self._.Polygon={} -for i=1,#Vec2Array do -self._.Polygon[i]={} -self._.Polygon[i].x=Vec2Array[i].x -self._.Polygon[i].y=Vec2Array[i].y -end -self._Triangles=self:_Triangulate() -self.SurfaceArea=self:_CalculateSurfaceArea() -return self -end -function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array) -self._.Polygon={} -for i=1,#Vec3Array do -self._.Polygon[i]={} -self._.Polygon[i].x=Vec3Array[i].x -self._.Polygon[i].y=Vec3Array[i].z -end -self._Triangles=self:_Triangulate() -self.SurfaceArea=self:_CalculateSurfaceArea() -return self -end -function ZONE_POLYGON_BASE:_CalculateSurfaceArea() -local area=0 -for _,triangle in pairs(self._Triangles)do -area=area+triangle.SurfaceArea -end -return area -end -function ZONE_POLYGON_BASE:GetVec2() -self:F(self.ZoneName) -local Bounds=self:GetBoundingSquare() -return{x=(Bounds.x2+Bounds.x1)/2,y=(Bounds.y2+Bounds.y1)/2} -end -function ZONE_POLYGON_BASE:GetVertexVec2(Index) -return self._.Polygon[Index or 1] -end -function ZONE_POLYGON_BASE:GetVertexVec3(Index) -local vec2=self:GetVertexVec2(Index) -if vec2 then -local vec3={x=vec2.x,y=land.getHeight(vec2),z=vec2.y} -return vec3 -end -return nil -end -function ZONE_POLYGON_BASE:GetVertexCoordinate(Index) -local vec2=self:GetVertexVec2(Index) -if vec2 then -local coord=COORDINATE:NewFromVec2(vec2) -return coord -end -return nil -end -function ZONE_POLYGON_BASE:GetVerticiesVec2() -return self._.Polygon -end -function ZONE_POLYGON_BASE:GetVerticiesVec3() -local coords={} -for i,vec2 in ipairs(self._.Polygon)do -local vec3={x=vec2.x,y=land.getHeight(vec2),z=vec2.y} -table.insert(coords,vec3) -end -return coords -end -function ZONE_POLYGON_BASE:GetVerticiesCoordinates() -local coords={} -for i,vec2 in ipairs(self._.Polygon)do -local coord=COORDINATE:NewFromVec2(vec2) -table.insert(coords,coord) -end -return coords -end -function ZONE_POLYGON_BASE:Flush() -self:F2() -self:F({Polygon=self.ZoneName,Coordinates=self._.Polygon}) -return self -end -function ZONE_POLYGON_BASE:BoundZone(UnBound) -local i -local j -local Segments=10 -i=1 -j=#self._.Polygon -while i<=#self._.Polygon do -self:T({i,j,self._.Polygon[i],self._.Polygon[j]}) -local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x -local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y -for Segment=0,Segments do -local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments) -local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments) -local Tire={ -["country"]="USA", -["category"]="Fortifications", -["canCargo"]=false, -["shape_name"]="H-tyre_B_WF", -["type"]="Black_Tyre_WF", -["y"]=PointY, -["x"]=PointX, -["name"]=string.format("%s-Tire #%0d",self:GetName(),((i-1)*Segments)+Segment), -["heading"]=0, -} -local Group=coalition.addStaticObject(country.id.USA,Tire) -if UnBound and UnBound==true then -Group:destroy() -end -end -j=i -i=i+1 -end -return self -end -function ZONE_POLYGON_BASE:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,IncludeTriangles) -if self._.Polygon and#self._.Polygon>=3 then -Coalition=Coalition or self:GetDrawCoalition() -self:SetDrawCoalition(Coalition) -Color=Color or self:GetColorRGB() -Alpha=Alpha or self:GetColorAlpha() -FillColor=FillColor or self:GetFillColorRGB() -FillAlpha=FillAlpha or self:GetFillColorAlpha() -if FillColor then -self:ReFill(FillColor,FillAlpha) -end -if Color then -self:ReDrawBorderline(Color,Alpha,LineType) -end -end -if false then -local coords=self:GetVerticiesCoordinates() -local coord=coords[1] -table.remove(coords,1) -coord:MarkupToAllFreeForm(coords,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,"Drew Polygon") -if true then -return -end -end -return self -end -function ZONE_POLYGON_BASE:ReFill(Color,Alpha) -local color=Color or self:GetFillColorRGB()or{1,0,0} -local alpha=Alpha or self:GetFillColorAlpha()or 1 -local coalition=self:GetDrawCoalition()or-1 -if#self.FillTriangles>0 then -for _,triangle in pairs(self._Triangles)do -triangle:UndrawZone() -end -for _,_value in pairs(self.FillTriangles)do -table.remove_by_value(self.DrawID,_value) -end -self.FillTriangles=nil -self.FillTriangles={} -end -for _,triangle in pairs(self._Triangles)do -local draw_ids=triangle:Fill(coalition,color,alpha,nil) -self.FillTriangles=draw_ids -table.combine(self.DrawID,draw_ids) -end -return self -end -function ZONE_POLYGON_BASE:ReDrawBorderline(Color,Alpha,LineType) -local color=Color or self:GetFillColorRGB()or{1,0,0} -local alpha=Alpha or self:GetFillColorAlpha()or 1 -local coalition=self:GetDrawCoalition()or-1 -local linetype=LineType or 1 -if#self.Borderlines>0 then -for _,MarkID in pairs(self.Borderlines)do -trigger.action.removeMark(MarkID) -end -for _,_value in pairs(self.Borderlines)do -table.remove_by_value(self.DrawID,_value) -end -self.Borderlines=nil -self.Borderlines={} -end -local coords=self:GetVerticiesCoordinates() -for i=1,#coords do -local c1=coords[i] -local c2=coords[i%#coords+1] -local newID=c1:LineToAll(c2,coalition,color,alpha,linetype,nil) -self.DrawID[#self.DrawID+1]=newID -self.Borderlines[#self.Borderlines+1]=newID -end -return self -end -function ZONE_POLYGON_BASE:GetSurfaceArea() -return self.SurfaceArea -end -function ZONE_POLYGON_BASE:GetRadius() -local center=self:GetVec2() -local radius=0 -for _,_vec2 in pairs(self._.Polygon)do -local vec2=_vec2 -local r=UTILS.VecDist2D(center,vec2) -if r>radius then -radius=r -end -end -return radius -end -function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName,DoNotRegisterZone) -local center=self:GetVec2() -local radius=self:GetRadius() -local zone=ZONE_RADIUS:New(ZoneName or self.ZoneName,center,radius,DoNotRegisterZone) -return zone -end -function ZONE_POLYGON_BASE:GetZoneQuad(ZoneName,DoNotRegisterZone) -local vec1,vec3=self:GetBoundingVec2() -local vec2={x=vec1.x,y=vec3.y} -local vec4={x=vec3.x,y=vec1.y} -local zone=ZONE_POLYGON_BASE:New(ZoneName or self.ZoneName,{vec1,vec2,vec3,vec4}) -return zone -end -function ZONE_POLYGON_BASE:RemoveJunk(Height) -Height=Height or 1000 -local vec2SW,vec2NE=self:GetBoundingVec2() -local vec3SW={x=vec2SW.x,y=-Height,z=vec2SW.y} -local vec3NE={x=vec2NE.x,y=Height,z=vec2NE.y} -local volume={ -id=world.VolumeType.BOX, -params={ -min=vec3SW, -max=vec3SW -} -} -local n=world.removeJunk(volume) -return n -end -function ZONE_POLYGON_BASE:SmokeZone(SmokeColor,Segments) -self:F2(SmokeColor) -Segments=Segments or 10 -local i=1 -local j=#self._.Polygon -while i<=#self._.Polygon do -self:T({i,j,self._.Polygon[i],self._.Polygon[j]}) -local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x -local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y -for Segment=0,Segments do -local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments) -local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments) -POINT_VEC2:New(PointX,PointY):Smoke(SmokeColor) -end -j=i -i=i+1 -end -return self -end -function ZONE_POLYGON_BASE:FlareZone(FlareColor,Segments,Azimuth,AddHeight) -self:F2(FlareColor) -Segments=Segments or 10 -AddHeight=AddHeight or 0 -local i=1 -local j=#self._.Polygon -while i<=#self._.Polygon do -self:T({i,j,self._.Polygon[i],self._.Polygon[j]}) -local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x -local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y -for Segment=0,Segments do -local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments) -local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments) -POINT_VEC2:New(PointX,PointY,AddHeight):Flare(FlareColor,Azimuth) -end -j=i -i=i+1 -end -return self -end -function ZONE_POLYGON_BASE:IsVec2InZone(Vec2) -self:F2(Vec2) -if not Vec2 then return false end -local Next -local Prev -local InPolygon=false -Next=1 -Prev=#self._.Polygon -while Next<=#self._.Polygon do -self:T({Next,Prev,self._.Polygon[Next],self._.Polygon[Prev]}) -if(((self._.Polygon[Next].y>Vec2.y)~=(self._.Polygon[Prev].y>Vec2.y))and -(Vec2.x<(self._.Polygon[Prev].x-self._.Polygon[Next].x)*(Vec2.y-self._.Polygon[Next].y)/(self._.Polygon[Prev].y-self._.Polygon[Next].y)+self._.Polygon[Next].x) -)then -InPolygon=not InPolygon -end -self:T2({InPolygon=InPolygon}) -Prev=Next -Next=Next+1 -end -self:T({InPolygon=InPolygon}) -return InPolygon -end -function ZONE_POLYGON_BASE:IsVec3InZone(Vec3) -self:F2(Vec3) -if not Vec3 then return false end -local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z}) -return InZone -end -function ZONE_POLYGON_BASE:GetRandomVec2() -local weights={} -for _,triangle in pairs(self._Triangles)do -weights[triangle]=triangle.SurfaceArea/self.SurfaceArea -end -local random_weight=math.random() -local accumulated_weight=0 -for triangle,weight in pairs(weights)do -accumulated_weight=accumulated_weight+weight -if accumulated_weight>=random_weight then -return triangle:GetRandomVec2() -end -end -end -function ZONE_POLYGON_BASE:GetRandomPointVec2() -self:F2() -local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2()) -self:T2(PointVec2) -return PointVec2 -end -function ZONE_POLYGON_BASE:GetRandomPointVec3() -self:F2() -local PointVec3=POINT_VEC3:NewFromVec2(self:GetRandomVec2()) -self:T2(PointVec3) -return PointVec3 -end -function ZONE_POLYGON_BASE:GetRandomCoordinate() -self:F2() -local Coordinate=COORDINATE:NewFromVec2(self:GetRandomVec2()) -self:T2(Coordinate) -return Coordinate -end -function ZONE_POLYGON_BASE:GetBoundingSquare() -local x1=self._.Polygon[1].x -local y1=self._.Polygon[1].y -local x2=self._.Polygon[1].x -local y2=self._.Polygon[1].y -for i=2,#self._.Polygon do -self:T2({self._.Polygon[i],x1,y1,x2,y2}) -x1=(x1>self._.Polygon[i].x)and self._.Polygon[i].x or x1 -x2=(x2self._.Polygon[i].y)and self._.Polygon[i].y or y1 -y2=(y2self._.Polygon[i].x)and self._.Polygon[i].x or x1 -x2=(x2self._.Polygon[i].y)and self._.Polygon[i].y or y1 -y2=(y21 and self:GetScannedCoalition(Coalition)==true -end -function ZONE_POLYGON:IsNoneInZoneOfCoalition(Coalition) -return self:GetScannedCoalition(Coalition)==nil -end -function ZONE_POLYGON:IsNoneInZone() -return self:CountScannedCoalitions()==0 -end -end -do -ZONE_ELASTIC={ -ClassName="ZONE_ELASTIC", -points={}, -setGroups={} -} -function ZONE_ELASTIC:New(ZoneName,Points) -local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(ZoneName,Points)) -_EVENTDISPATCHER:CreateEventNewZone(self) -if Points then -self.points=Points -end -return self -end -function ZONE_ELASTIC:AddVertex2D(Vec2) -table.insert(self.points,Vec2) -return self -end -function ZONE_ELASTIC:AddVertex3D(Vec3) -table.insert(self.points,{x=Vec3.x,y=Vec3.z}) -return self -end -function ZONE_ELASTIC:AddSetGroup(GroupSet) -table.insert(self.setGroups,GroupSet) -return self -end -function ZONE_ELASTIC:Update(Delay,Draw) -self:T(string.format("Updating ZONE_ELASTIC %s",tostring(self.ZoneName))) -local points=UTILS.DeepCopy(self.points or{}) -if self.setGroups then -for _,_setGroup in pairs(self.setGroups)do -local setGroup=_setGroup -for _,_group in pairs(setGroup.Set)do -local group=_group -if group and group:IsAlive()then -table.insert(points,group:GetVec2()) -end -end -end -end -self._.Polygon=self:_ConvexHull(points) -if Draw~=false then -if self.DrawID or Draw==true then -self:UndrawZone() -self:DrawZone() -end -end -return self -end -function ZONE_ELASTIC:StartUpdate(Tstart,dT,Tstop,Draw) -self.updateID=self:ScheduleRepeat(Tstart,dT,0,Tstop,ZONE_ELASTIC.Update,self,0,Draw) -return self -end -function ZONE_ELASTIC:StopUpdate(Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,ZONE_ELASTIC.StopUpdate,self) -else -if self.updateID then -self:ScheduleStop(self.updateID) -self.updateID=nil -end -end -return self -end -function ZONE_ELASTIC:_ConvexHull(pl) -if#pl==0 then -return{} -end -table.sort(pl,function(left,right) -return left.x(b.y-a.y)*(c.x-a.x) -end -for i,pt in pairs(pl)do -while#h>=2 and not ccw(h[#h-1],h[#h],pt)do -table.remove(h,#h) -end -table.insert(h,pt) -end -local t=#h+1 -for i=#pl,1,-1 do -local pt=pl[i] -while#h>=t and not ccw(h[#h-1],h[#h],pt)do -table.remove(h,#h) -end -table.insert(h,pt) -end -table.remove(h,#h) -return h -end -end -ZONE_OVAL={ -ClassName="OVAL", -ZoneName="", -MajorAxis=nil, -MinorAxis=nil, -Angle=0, -DrawPoly=nil -} -function ZONE_OVAL:New(name,vec2,major_axis,minor_axis,angle) -self=BASE:Inherit(self,ZONE_BASE:New()) -self.ZoneName=name -self.CenterVec2=vec2 -self.MajorAxis=major_axis -self.MinorAxis=minor_axis -self.Angle=angle or 0 -_DATABASE:AddZone(name,self) -return self -end -function ZONE_OVAL:NewFromDrawing(DrawingName) -self=BASE:Inherit(self,ZONE_BASE:New(DrawingName)) -for _,layer in pairs(env.mission.drawings.layers)do -for _,object in pairs(layer["objects"])do -if string.find(object["name"],DrawingName,1,true)then -if object["polygonMode"]=="oval"then -self.CenterVec2={x=object["mapX"],y=object["mapY"]} -self.MajorAxis=object["r1"] -self.MinorAxis=object["r2"] -self.Angle=object["angle"] -end -end -end -end -_DATABASE:AddZone(DrawingName,self) -return self -end -function ZONE_OVAL:GetMajorAxis() -return self.MajorAxis -end -function ZONE_OVAL:GetMinorAxis() -return self.MinorAxis -end -function ZONE_OVAL:GetAngle() -return self.Angle -end -function ZONE_OVAL:GetVec2() -return self.CenterVec2 -end -function ZONE_OVAL:IsVec2InZone(vec2) -local cos,sin=math.cos,math.sin -local dx=vec2.x-self.CenterVec2.x -local dy=vec2.y-self.CenterVec2.y -local rx=dx*cos(self.Angle)+dy*sin(self.Angle) -local ry=-dx*sin(self.Angle)+dy*cos(self.Angle) -return rx*rx/(self.MajorAxis*self.MajorAxis)+ry*ry/(self.MinorAxis*self.MinorAxis)<=1 -end -function ZONE_OVAL:GetBoundingSquare() -local min_x=self.CenterVec2.x-self.MajorAxis -local min_y=self.CenterVec2.y-self.MinorAxis -local max_x=self.CenterVec2.x+self.MajorAxis -local max_y=self.CenterVec2.y+self.MinorAxis -return{ -{x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y} -} -end -function ZONE_OVAL:PointsOnEdge(num_points) -num_points=num_points or 40 -local points={} -local dtheta=2*math.pi/num_points -for i=0,num_points-1 do -local theta=i*dtheta -local x=self.CenterVec2.x+self.MajorAxis*math.cos(theta)*math.cos(self.Angle)-self.MinorAxis*math.sin(theta)*math.sin(self.Angle) -local y=self.CenterVec2.y+self.MajorAxis*math.cos(theta)*math.sin(self.Angle)+self.MinorAxis*math.sin(theta)*math.cos(self.Angle) -table.insert(points,{x=x,y=y}) -end -return points -end -function ZONE_OVAL:GetRandomVec2() -local theta=math.rad(self.Angle) -local random_point=math.sqrt(math.random()) -local phi=math.random()*2*math.pi -local x_c=random_point*math.cos(phi) -local y_c=random_point*math.sin(phi) -local x_e=x_c*self.MajorAxis -local y_e=y_c*self.MinorAxis -local rx=(x_e*math.cos(theta)-y_e*math.sin(theta))+self.CenterVec2.x -local ry=(x_e*math.sin(theta)+y_e*math.cos(theta))+self.CenterVec2.y -return{x=rx,y=ry} -end -function ZONE_OVAL:GetRandomPointVec2() -return POINT_VEC2:NewFromVec2(self:GetRandomVec2()) -end -function ZONE_OVAL:GetRandomPointVec3() -return POINT_VEC2:NewFromVec3(self:GetRandomVec2()) -end -function ZONE_OVAL:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType) -Coalition=Coalition or self:GetDrawCoalition() -self:SetDrawCoalition(Coalition) -Color=Color or self:GetColorRGB() -Alpha=Alpha or 1 -self:SetColor(Color,Alpha) -FillColor=FillColor or self:GetFillColorRGB() -if not FillColor then -UTILS.DeepCopy(Color) -end -FillAlpha=FillAlpha or self:GetFillColorAlpha() -if not FillAlpha then -FillAlpha=0.15 -end -LineType=LineType or 1 -self:SetFillColor(FillColor,FillAlpha) -self.DrawPoly=ZONE_POLYGON:NewFromPointsArray(self.ZoneName,self:PointsOnEdge(80)) -self.DrawPoly:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType) -end -function ZONE_OVAL:UndrawZone() -if self.DrawPoly then -self.DrawPoly:UndrawZone() -end -end -do -ZONE_AIRBASE={ -ClassName="ZONE_AIRBASE", -} -function ZONE_AIRBASE:New(AirbaseName,Radius) -Radius=Radius or 4000 -local Airbase=AIRBASE:FindByName(AirbaseName) -local self=BASE:Inherit(self,ZONE_RADIUS:New(AirbaseName,Airbase:GetVec2(),Radius,true)) -self._.ZoneAirbase=Airbase -self._.ZoneVec2Cache=self._.ZoneAirbase:GetVec2() -if Airbase:IsShip()then -self.isShip=true -self.isHelipad=false -self.isAirdrome=false -elseif Airbase:IsHelipad()then -self.isShip=false -self.isHelipad=true -self.isAirdrome=false -elseif Airbase:IsAirdrome()then -self.isShip=false -self.isHelipad=false -self.isAirdrome=true -end -_EVENTDISPATCHER:CreateEventNewZone(self) -return self -end -function ZONE_AIRBASE:GetAirbase() -return self._.ZoneAirbase -end -function ZONE_AIRBASE:GetVec2() -self:F(self.ZoneName) -local ZoneVec2=nil -if self._.ZoneAirbase:IsAlive()then -ZoneVec2=self._.ZoneAirbase:GetVec2() -self._.ZoneVec2Cache=ZoneVec2 -else -ZoneVec2=self._.ZoneVec2Cache -end -self:T({ZoneVec2}) -return ZoneVec2 -end -function ZONE_AIRBASE:GetRandomPointVec2(inner,outer) -self:F(self.ZoneName,inner,outer) -local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2()) -self:T3({PointVec2}) -return PointVec2 -end -end -ZONE_DETECTION={ -ClassName="ZONE_DETECTION", -} -function ZONE_DETECTION:New(ZoneName,Detection,Radius) -local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName)) -self:F({ZoneName,Detection,Radius}) -self.Detection=Detection -self.Radius=Radius -return self -end -function ZONE_DETECTION:BoundZone(Points,CountryID,UnBound) -local Point={} -local Vec2=self:GetVec2() -Points=Points and Points or 360 -local Angle -local RadialBase=math.pi*2 -for Angle=0,360,(360/Points)do -local Radial=Angle*RadialBase/360 -Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() -Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() -local CountryName=_DATABASE.COUNTRY_NAME[CountryID] -local Tire={ -["country"]=CountryName, -["category"]="Fortifications", -["canCargo"]=false, -["shape_name"]="H-tyre_B_WF", -["type"]="Black_Tyre_WF", -["y"]=Point.y, -["x"]=Point.x, -["name"]=string.format("%s-Tire #%0d",self:GetName(),Angle), -["heading"]=0, -} -local Group=coalition.addStaticObject(CountryID,Tire) -if UnBound and UnBound==true then -Group:destroy() -end -end -return self -end -function ZONE_DETECTION:SmokeZone(SmokeColor,Points,AddHeight,AngleOffset) -self:F2(SmokeColor) -local Point={} -local Vec2=self:GetVec2() -AddHeight=AddHeight or 0 -AngleOffset=AngleOffset or 0 -Points=Points and Points or 360 -local Angle -local RadialBase=math.pi*2 -for Angle=0,360,360/Points do -local Radial=(Angle+AngleOffset)*RadialBase/360 -Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() -Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() -POINT_VEC2:New(Point.x,Point.y,AddHeight):Smoke(SmokeColor) -end -return self -end -function ZONE_DETECTION:FlareZone(FlareColor,Points,Azimuth,AddHeight) -self:F2({FlareColor,Azimuth}) -local Point={} -local Vec2=self:GetVec2() -AddHeight=AddHeight or 0 -Points=Points and Points or 360 -local Angle -local RadialBase=math.pi*2 -for Angle=0,360,360/Points do -local Radial=Angle*RadialBase/360 -Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() -Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() -POINT_VEC2:New(Point.x,Point.y,AddHeight):Flare(FlareColor,Azimuth) -end -return self -end -function ZONE_DETECTION:GetRadius() -self:F2(self.ZoneName) -self:T2({self.Radius}) -return self.Radius -end -function ZONE_DETECTION:SetRadius(Radius) -self:F2(self.ZoneName) -self.Radius=Radius -self:T2({self.Radius}) -return self.Radius -end -function ZONE_DETECTION:IsVec2InZone(Vec2) -self:F2(Vec2) -local Coordinates=self.Detection:GetDetectedItemCoordinates() -for CoordinateID,Coordinate in pairs(Coordinates)do -local ZoneVec2=Coordinate:GetVec2() -if ZoneVec2 then -if((Vec2.x-ZoneVec2.x)^2+(Vec2.y-ZoneVec2.y)^2)^0.5<=self:GetRadius()then -return true -end -end -end -return false -end -function ZONE_DETECTION:IsVec3InZone(Vec3) -self:F2(Vec3) -local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z}) -return InZone -end -DATABASE={ -ClassName="DATABASE", -Templates={ -Units={}, -Groups={}, -Statics={}, -ClientsByName={}, -ClientsByID={}, -}, -UNITS={}, -UNITS_Index={}, -STATICS={}, -GROUPS={}, -PLAYERS={}, -PLAYERSJOINED={}, -PLAYERUNITS={}, -CLIENTS={}, -CARGOS={}, -AIRBASES={}, -COUNTRY_ID={}, -COUNTRY_NAME={}, -NavPoints={}, -PLAYERSETTINGS={}, -ZONENAMES={}, -HITS={}, -DESTROYS={}, -ZONES={}, -ZONES_GOAL={}, -WAREHOUSES={}, -FLIGHTGROUPS={}, -FLIGHTCONTROLS={}, -OPSZONES={}, -PATHLINES={}, -STORAGES={}, -} -local _DATABASECoalition= -{ -[1]="Red", -[2]="Blue", -[3]="Neutral", -} -local _DATABASECategory= -{ -["plane"]=Unit.Category.AIRPLANE, -["helicopter"]=Unit.Category.HELICOPTER, -["vehicle"]=Unit.Category.GROUND_UNIT, -["ship"]=Unit.Category.SHIP, -["static"]=Unit.Category.STRUCTURE, -} -function DATABASE:New() -local self=BASE:Inherit(self,BASE:New()) -self:SetEventPriority(1) -self:HandleEvent(EVENTS.Birth,self._EventOnBirth) -self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventOnPlayerEnterUnit) -self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) -self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) -self:HandleEvent(EVENTS.RemoveUnit,self._EventOnDeadOrCrash) -self:HandleEvent(EVENTS.Hit,self.AccountHits) -self:HandleEvent(EVENTS.NewCargo) -self:HandleEvent(EVENTS.DeleteCargo) -self:HandleEvent(EVENTS.NewZone) -self:HandleEvent(EVENTS.DeleteZone) -self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventOnPlayerLeaveUnit) -self:_RegisterTemplates() -self:_RegisterGroupsAndUnits() -self:_RegisterClients() -self:_RegisterStatics() -self.UNITS_Position=0 -return self -end -function DATABASE:FindUnit(UnitName) -local UnitFound=self.UNITS[UnitName] -return UnitFound -end -function DATABASE:AddUnit(DCSUnitName) -if not self.UNITS[DCSUnitName]then -self:T({"Add UNIT:",DCSUnitName}) -self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName) -end -return self.UNITS[DCSUnitName] -end -function DATABASE:DeleteUnit(DCSUnitName) -self.UNITS[DCSUnitName]=nil -end -function DATABASE:AddStatic(DCSStaticName) -if not self.STATICS[DCSStaticName]then -self.STATICS[DCSStaticName]=STATIC:Register(DCSStaticName) -return self.STATICS[DCSStaticName] -end -return nil -end -function DATABASE:DeleteStatic(DCSStaticName) -self.STATICS[DCSStaticName]=nil -end -function DATABASE:FindStatic(StaticName) -local StaticFound=self.STATICS[StaticName] -return StaticFound -end -function DATABASE:AddAirbase(AirbaseName) -if not self.AIRBASES[AirbaseName]then -self.AIRBASES[AirbaseName]=AIRBASE:Register(AirbaseName) -end -return self.AIRBASES[AirbaseName] -end -function DATABASE:DeleteAirbase(AirbaseName) -self.AIRBASES[AirbaseName]=nil -end -function DATABASE:FindAirbase(AirbaseName) -local AirbaseFound=self.AIRBASES[AirbaseName] -return AirbaseFound -end -function DATABASE:AddStorage(AirbaseName) -if not self.STORAGES[AirbaseName]then -self.STORAGES[AirbaseName]=STORAGE:New(AirbaseName) -end -return self.STORAGES[AirbaseName] -end -function DATABASE:DeleteStorage(AirbaseName) -self.STORAGES[AirbaseName]=nil -end -function DATABASE:FindStorage(AirbaseName) -local storage=self.STORAGES[AirbaseName] -return storage -end -do -function DATABASE:FindZone(ZoneName) -local ZoneFound=self.ZONES[ZoneName] -return ZoneFound -end -function DATABASE:AddZone(ZoneName,Zone) -if not self.ZONES[ZoneName]then -self.ZONES[ZoneName]=Zone -end -end -function DATABASE:DeleteZone(ZoneName) -self.ZONES[ZoneName]=nil -end -function DATABASE:AddPathline(PathlineName,Pathline) -if not self.PATHLINES[PathlineName]then -self.PATHLINES[PathlineName]=Pathline -end -end -function DATABASE:FindPathline(PathlineName) -local pathline=self.PATHLINES[PathlineName] -return pathline -end -function DATABASE:DeletePathline(PathlineName) -self.PATHLINES[PathlineName]=nil -return self -end -function DATABASE:_RegisterZones() -for ZoneID,ZoneData in pairs(env.mission.triggers.zones)do -local ZoneName=ZoneData.name -local color=ZoneData.color or{1,0,0,0.15} -local Zone=nil -if ZoneData.type==0 then -self:I(string.format("Register ZONE: %s (Circular)",ZoneName)) -Zone=ZONE:New(ZoneName) -else -self:I(string.format("Register ZONE: %s (Polygon, Quad)",ZoneName)) -Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,ZoneData.verticies) -end -if Zone then -Zone.Color=color -Zone.ZoneID=ZoneData.zoneId -local ZoneProperties=ZoneData.properties or nil -Zone.Properties={} -if ZoneName and ZoneProperties then -for _,ZoneProp in ipairs(ZoneProperties)do -if ZoneProp.key then -Zone.Properties[ZoneProp.key]=ZoneProp.value -end -end -end -self.ZONENAMES[ZoneName]=ZoneName -self:AddZone(ZoneName,Zone) -end -end -for ZoneGroupName,ZoneGroup in pairs(self.GROUPS)do -if ZoneGroupName:match("#ZONE_POLYGON")then -local ZoneName1=ZoneGroupName:match("(.*)#ZONE_POLYGON") -local ZoneName2=ZoneGroupName:match(".*#ZONE_POLYGON(.*)") -local ZoneName=ZoneName1..(ZoneName2 or"") -self:I(string.format("Register ZONE: %s (Polygon)",ZoneName)) -local Zone_Polygon=ZONE_POLYGON:New(ZoneName,ZoneGroup) -Zone_Polygon:SetColor({1,0,0},0.15) -self.ZONENAMES[ZoneName]=ZoneName -self:AddZone(ZoneName,Zone_Polygon) -end -end -if env.mission.drawings and env.mission.drawings.layers then -for layerID,layerData in pairs(env.mission.drawings.layers or{})do -for objectID,objectData in pairs(layerData.objects or{})do -if objectData.polygonMode and(objectData.polygonMode=="free")and objectData.points and#objectData.points>=4 then -local ZoneName=objectData.name or"Unknown free Polygon Drawing" -local vec2={x=objectData.mapX,y=objectData.mapY} -local points=UTILS.DeepCopy(objectData.points) -for i,_point in pairs(points)do -local point=_point -points[i]=UTILS.Vec2Add(point,vec2) -end -table.remove(points,#points) -self:I(string.format("Register ZONE: %s (Polygon (free) drawing with %d vertices)",ZoneName,#points)) -local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,points) -Zone:SetColor({1,0,0},0.15) -Zone:SetFillColor({1,0,0},0.15) -if objectData.colorString then -local color=string.gsub(objectData.colorString,"^0x","") -local r=tonumber(string.sub(color,1,2),16)/255 -local g=tonumber(string.sub(color,3,4),16)/255 -local b=tonumber(string.sub(color,5,6),16)/255 -local a=tonumber(string.sub(color,7,8),16)/255 -Zone:SetColor({r,g,b},a) -end -if objectData.fillColorString then -local color=string.gsub(objectData.colorString,"^0x","") -local r=tonumber(string.sub(color,1,2),16)/255 -local g=tonumber(string.sub(color,3,4),16)/255 -local b=tonumber(string.sub(color,5,6),16)/255 -local a=tonumber(string.sub(color,7,8),16)/255 -Zone:SetFillColor({r,g,b},a) -end -self.ZONENAMES[ZoneName]=ZoneName -self:AddZone(ZoneName,Zone) -elseif objectData.polygonMode and objectData.polygonMode=="rect"then -local ZoneName=objectData.name or"Unknown rect Polygon Drawing" -local vec2={x=objectData.mapX,y=objectData.mapY} -local w=objectData.width -local h=objectData.height -local points={} -points[1]={x=vec2.x-h/2,y=vec2.y+w/2} -points[2]={x=vec2.x+h/2,y=vec2.y+w/2} -points[3]={x=vec2.x+h/2,y=vec2.y-w/2} -points[4]={x=vec2.x-h/2,y=vec2.y-w/2} -self:I(string.format("Register ZONE: %s (Polygon (rect) drawing with %d vertices)",ZoneName,#points)) -local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,points) -Zone:SetColor({1,0,0},0.15) -if objectData.colorString then -local color=string.gsub(objectData.colorString,"^0x","") -local r=tonumber(string.sub(color,1,2),16)/255 -local g=tonumber(string.sub(color,3,4),16)/255 -local b=tonumber(string.sub(color,5,6),16)/255 -local a=tonumber(string.sub(color,7,8),16)/255 -Zone:SetColor({r,g,b},a) -end -if objectData.fillColorString then -local color=string.gsub(objectData.colorString,"^0x","") -local r=tonumber(string.sub(color,1,2),16)/255 -local g=tonumber(string.sub(color,3,4),16)/255 -local b=tonumber(string.sub(color,5,6),16)/255 -local a=tonumber(string.sub(color,7,8),16)/255 -Zone:SetFillColor({r,g,b},a) -end -self.ZONENAMES[ZoneName]=ZoneName -self:AddZone(ZoneName,Zone) -elseif objectData.lineMode and(objectData.lineMode=="segments"or objectData.lineMode=="segment"or objectData.lineMode=="free")and objectData.points and#objectData.points>=2 then -local Name=objectData.name or"Unknown Line Drawing" -local vec2={x=objectData.mapX,y=objectData.mapY} -local points=UTILS.DeepCopy(objectData.points) -for i,_point in pairs(points)do -local point=_point -points[i]=UTILS.Vec2Add(point,vec2) -end -self:I(string.format("Register PATHLINE: %s (Line drawing with %d points)",Name,#points)) -local Pathline=PATHLINE:NewFromVec2Array(Name,points) -self:AddPathline(Name,Pathline) -end -end -end -end -end -end -do -function DATABASE:FindZoneGoal(ZoneName) -local ZoneFound=self.ZONES_GOAL[ZoneName] -return ZoneFound -end -function DATABASE:AddZoneGoal(ZoneName,Zone) -if not self.ZONES_GOAL[ZoneName]then -self.ZONES_GOAL[ZoneName]=Zone -end -end -function DATABASE:DeleteZoneGoal(ZoneName) -self.ZONES_GOAL[ZoneName]=nil -end -end -do -function DATABASE:FindOpsZone(ZoneName) -local ZoneFound=self.OPSZONES[ZoneName] -return ZoneFound -end -function DATABASE:AddOpsZone(OpsZone) -if OpsZone then -local ZoneName=OpsZone:GetName() -if not self.OPSZONES[ZoneName]then -self.OPSZONES[ZoneName]=OpsZone -end -end -end -function DATABASE:DeleteOpsZone(ZoneName) -self.OPSZONES[ZoneName]=nil -end -end -do -function DATABASE:AddCargo(Cargo) -if not self.CARGOS[Cargo.Name]then -self.CARGOS[Cargo.Name]=Cargo -end -end -function DATABASE:DeleteCargo(CargoName) -self.CARGOS[CargoName]=nil -end -function DATABASE:FindCargo(CargoName) -local CargoFound=self.CARGOS[CargoName] -return CargoFound -end -function DATABASE:IsCargo(TemplateName) -TemplateName=env.getValueDictByKey(TemplateName) -local Cargo=TemplateName:match("#(CARGO)") -return Cargo and Cargo=="CARGO" -end -function DATABASE:_RegisterCargos() -local Groups=UTILS.DeepCopy(self.GROUPS) -for CargoGroupName,CargoGroup in pairs(Groups)do -if self:IsCargo(CargoGroupName)then -local CargoInfo=CargoGroupName:match("#CARGO(.*)") -local CargoParam=CargoInfo and CargoInfo:match("%((.*)%)") -local CargoName1=CargoGroupName:match("(.*)#CARGO%(.*%)") -local CargoName2=CargoGroupName:match(".*#CARGO%(.*%)(.*)") -local CargoName=CargoName1..(CargoName2 or"") -local Type=CargoParam and CargoParam:match("T=([%a%d ]+),?") -local Name=CargoParam and CargoParam:match("N=([%a%d]+),?")or CargoName -local LoadRadius=CargoParam and tonumber(CargoParam:match("RR=([%a%d]+),?")) -local NearRadius=CargoParam and tonumber(CargoParam:match("NR=([%a%d]+),?")) -self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) -CARGO_GROUP:New(CargoGroup,Type,Name,LoadRadius,NearRadius) -end -end -for CargoStaticName,CargoStatic in pairs(self.STATICS)do -if self:IsCargo(CargoStaticName)then -local CargoInfo=CargoStaticName:match("#CARGO(.*)") -local CargoParam=CargoInfo and CargoInfo:match("%((.*)%)") -local CargoName=CargoStaticName:match("(.*)#CARGO") -local Type=CargoParam and CargoParam:match("T=([%a%d ]+),?") -local Category=CargoParam and CargoParam:match("C=([%a%d ]+),?") -local Name=CargoParam and CargoParam:match("N=([%a%d]+),?")or CargoName -local LoadRadius=CargoParam and tonumber(CargoParam:match("RR=([%a%d]+),?")) -local NearRadius=CargoParam and tonumber(CargoParam:match("NR=([%a%d]+),?")) -if Category=="SLING"then -self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) -CARGO_SLINGLOAD:New(CargoStatic,Type,Name,LoadRadius,NearRadius) -else -if Category=="CRATE"then -self:I({"Register CargoCrate:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) -CARGO_CRATE:New(CargoStatic,Type,Name,LoadRadius,NearRadius) -end -end -end -end -end -end -function DATABASE:FindClient(ClientName) -local ClientFound=self.CLIENTS[ClientName] -return ClientFound -end -function DATABASE:AddClient(ClientName) -if not self.CLIENTS[ClientName]then -self.CLIENTS[ClientName]=CLIENT:Register(ClientName) -end -return self.CLIENTS[ClientName] -end -function DATABASE:FindGroup(GroupName) -local GroupFound=self.GROUPS[GroupName] -return GroupFound -end -function DATABASE:AddGroup(GroupName) -if not self.GROUPS[GroupName]then -self:T({"Add GROUP:",GroupName}) -self.GROUPS[GroupName]=GROUP:Register(GroupName) -end -return self.GROUPS[GroupName] -end -function DATABASE:AddPlayer(UnitName,PlayerName) -if PlayerName then -self:T({"Add player for unit:",UnitName,PlayerName}) -self.PLAYERS[PlayerName]=UnitName -self.PLAYERUNITS[PlayerName]=self:FindUnit(UnitName) -self.PLAYERSJOINED[PlayerName]=PlayerName -end -end -function DATABASE:DeletePlayer(UnitName,PlayerName) -if PlayerName then -self:T({"Clean player:",PlayerName}) -self.PLAYERS[PlayerName]=nil -self.PLAYERUNITS[PlayerName]=nil -end -end -function DATABASE:GetPlayers() -return self.PLAYERS -end -function DATABASE:GetPlayerUnits() -return self.PLAYERUNITS -end -function DATABASE:GetPlayersJoined() -return self.PLAYERSJOINED -end -function DATABASE:Spawn(SpawnTemplate) -self:F(SpawnTemplate.name) -self:T({SpawnTemplate.SpawnCountryID,SpawnTemplate.SpawnCategoryID}) -local SpawnCoalitionID=SpawnTemplate.CoalitionID -local SpawnCountryID=SpawnTemplate.CountryID -local SpawnCategoryID=SpawnTemplate.CategoryID -SpawnTemplate.CoalitionID=nil -SpawnTemplate.CountryID=nil -SpawnTemplate.CategoryID=nil -self:_RegisterGroupTemplate(SpawnTemplate,SpawnCoalitionID,SpawnCategoryID,SpawnCountryID) -self:T3(SpawnTemplate) -coalition.addGroup(SpawnCountryID,SpawnCategoryID,SpawnTemplate) -SpawnTemplate.CoalitionID=SpawnCoalitionID -SpawnTemplate.CountryID=SpawnCountryID -SpawnTemplate.CategoryID=SpawnCategoryID -local SpawnGroup=self:AddGroup(SpawnTemplate.name) -for UnitID,UnitData in pairs(SpawnTemplate.units)do -self:AddUnit(UnitData.name) -end -return SpawnGroup -end -function DATABASE:SetStatusGroup(GroupName,Status) -self:F2(Status) -self.Templates.Groups[GroupName].Status=Status -end -function DATABASE:GetStatusGroup(GroupName) -self:F2(GroupName) -if self.Templates.Groups[GroupName]then -return self.Templates.Groups[GroupName].Status -else -return"" -end -end -function DATABASE:_RegisterGroupTemplate(GroupTemplate,CoalitionSide,CategoryID,CountryID,GroupName) -local GroupTemplateName=GroupName or env.getValueDictByKey(GroupTemplate.name) -if not self.Templates.Groups[GroupTemplateName]then -self.Templates.Groups[GroupTemplateName]={} -self.Templates.Groups[GroupTemplateName].Status=nil -end -if GroupTemplate.route and GroupTemplate.route.spans then -GroupTemplate.route.spans=nil -end -GroupTemplate.CategoryID=CategoryID -GroupTemplate.CoalitionID=CoalitionSide -GroupTemplate.CountryID=CountryID -self.Templates.Groups[GroupTemplateName].GroupName=GroupTemplateName -self.Templates.Groups[GroupTemplateName].Template=GroupTemplate -self.Templates.Groups[GroupTemplateName].groupId=GroupTemplate.groupId -self.Templates.Groups[GroupTemplateName].UnitCount=#GroupTemplate.units -self.Templates.Groups[GroupTemplateName].Units=GroupTemplate.units -self.Templates.Groups[GroupTemplateName].CategoryID=CategoryID -self.Templates.Groups[GroupTemplateName].CoalitionID=CoalitionSide -self.Templates.Groups[GroupTemplateName].CountryID=CountryID -local UnitNames={} -for unit_num,UnitTemplate in pairs(GroupTemplate.units)do -UnitTemplate.name=env.getValueDictByKey(UnitTemplate.name) -self.Templates.Units[UnitTemplate.name]={} -self.Templates.Units[UnitTemplate.name].UnitName=UnitTemplate.name -self.Templates.Units[UnitTemplate.name].Template=UnitTemplate -self.Templates.Units[UnitTemplate.name].GroupName=GroupTemplateName -self.Templates.Units[UnitTemplate.name].GroupTemplate=GroupTemplate -self.Templates.Units[UnitTemplate.name].GroupId=GroupTemplate.groupId -self.Templates.Units[UnitTemplate.name].CategoryID=CategoryID -self.Templates.Units[UnitTemplate.name].CoalitionID=CoalitionSide -self.Templates.Units[UnitTemplate.name].CountryID=CountryID -if UnitTemplate.skill and(UnitTemplate.skill=="Client"or UnitTemplate.skill=="Player")then -self.Templates.ClientsByName[UnitTemplate.name]=UnitTemplate -self.Templates.ClientsByName[UnitTemplate.name].CategoryID=CategoryID -self.Templates.ClientsByName[UnitTemplate.name].CoalitionID=CoalitionSide -self.Templates.ClientsByName[UnitTemplate.name].CountryID=CountryID -self.Templates.ClientsByID[UnitTemplate.unitId]=UnitTemplate -end -UnitNames[#UnitNames+1]=self.Templates.Units[UnitTemplate.name].UnitName -end -self:T({Group=self.Templates.Groups[GroupTemplateName].GroupName, -Coalition=self.Templates.Groups[GroupTemplateName].CoalitionID, -Category=self.Templates.Groups[GroupTemplateName].CategoryID, -Country=self.Templates.Groups[GroupTemplateName].CountryID, -Units=UnitNames -} -) -end -function DATABASE:GetGroupTemplate(GroupName) -local GroupTemplate=self.Templates.Groups[GroupName].Template -GroupTemplate.SpawnCoalitionID=self.Templates.Groups[GroupName].CoalitionID -GroupTemplate.SpawnCategoryID=self.Templates.Groups[GroupName].CategoryID -GroupTemplate.SpawnCountryID=self.Templates.Groups[GroupName].CountryID -return GroupTemplate -end -function DATABASE:_RegisterStaticTemplate(StaticTemplate,CoalitionID,CategoryID,CountryID) -local StaticTemplate=UTILS.DeepCopy(StaticTemplate) -local StaticTemplateGroupName=env.getValueDictByKey(StaticTemplate.name) -local StaticTemplateName=StaticTemplate.units[1].name -self.Templates.Statics[StaticTemplateName]=self.Templates.Statics[StaticTemplateName]or{} -StaticTemplate.CategoryID=CategoryID -StaticTemplate.CoalitionID=CoalitionID -StaticTemplate.CountryID=CountryID -self.Templates.Statics[StaticTemplateName].StaticName=StaticTemplateGroupName -self.Templates.Statics[StaticTemplateName].GroupTemplate=StaticTemplate -self.Templates.Statics[StaticTemplateName].UnitTemplate=StaticTemplate.units[1] -self.Templates.Statics[StaticTemplateName].CategoryID=CategoryID -self.Templates.Statics[StaticTemplateName].CoalitionID=CoalitionID -self.Templates.Statics[StaticTemplateName].CountryID=CountryID -self:T({Static=self.Templates.Statics[StaticTemplateName].StaticName, -Coalition=self.Templates.Statics[StaticTemplateName].CoalitionID, -Category=self.Templates.Statics[StaticTemplateName].CategoryID, -Country=self.Templates.Statics[StaticTemplateName].CountryID -} -) -self:AddStatic(StaticTemplateName) -return self -end -function DATABASE:GetStaticGroupTemplate(StaticName) -if self.Templates.Statics[StaticName]then -local StaticTemplate=self.Templates.Statics[StaticName].GroupTemplate -return StaticTemplate,self.Templates.Statics[StaticName].CoalitionID,self.Templates.Statics[StaticName].CategoryID,self.Templates.Statics[StaticName].CountryID -else -self:E("ERROR: Static group template does NOT exist for static "..tostring(StaticName)) -return nil -end -end -function DATABASE:GetStaticUnitTemplate(StaticName) -if self.Templates.Statics[StaticName]then -local UnitTemplate=self.Templates.Statics[StaticName].UnitTemplate -return UnitTemplate,self.Templates.Statics[StaticName].CoalitionID,self.Templates.Statics[StaticName].CategoryID,self.Templates.Statics[StaticName].CountryID -else -self:E("ERROR: Static unit template does NOT exist for static "..tostring(StaticName)) -return nil -end -end -function DATABASE:GetGroupNameFromUnitName(UnitName) -if self.Templates.Units[UnitName]then -return self.Templates.Units[UnitName].GroupName -else -self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) -return nil -end -end -function DATABASE:GetGroupTemplateFromUnitName(UnitName) -if self.Templates.Units[UnitName]then -return self.Templates.Units[UnitName].GroupTemplate -else -self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) -return nil -end -end -function DATABASE:GetUnitTemplateFromUnitName(UnitName) -if self.Templates.Units[UnitName]then -return self.Templates.Units[UnitName] -else -self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) -return nil -end -end -function DATABASE:GetCoalitionFromClientTemplate(ClientName) -return self.Templates.ClientsByName[ClientName].CoalitionID -end -function DATABASE:GetCategoryFromClientTemplate(ClientName) -return self.Templates.ClientsByName[ClientName].CategoryID -end -function DATABASE:GetCountryFromClientTemplate(ClientName) -return self.Templates.ClientsByName[ClientName].CountryID -end -function DATABASE:GetCoalitionFromAirbase(AirbaseName) -return self.AIRBASES[AirbaseName]:GetCoalition() -end -function DATABASE:GetCategoryFromAirbase(AirbaseName) -return self.AIRBASES[AirbaseName]:GetAirbaseCategory() -end -function DATABASE:_RegisterPlayers() -local CoalitionsData={AlivePlayersRed=coalition.getPlayers(coalition.side.RED),AlivePlayersBlue=coalition.getPlayers(coalition.side.BLUE),AlivePlayersNeutral=coalition.getPlayers(coalition.side.NEUTRAL)} -for CoalitionId,CoalitionData in pairs(CoalitionsData)do -for UnitId,UnitData in pairs(CoalitionData)do -self:T3({"UnitData:",UnitData}) -if UnitData and UnitData:isExist()then -local UnitName=UnitData:getName() -local PlayerName=UnitData:getPlayerName() -if not self.PLAYERS[PlayerName]then -self:I({"Add player for unit:",UnitName,PlayerName}) -self:AddPlayer(UnitName,PlayerName) -end -end -end -end -return self -end -function DATABASE:_RegisterGroupsAndUnits() -local CoalitionsData={GroupsRed=coalition.getGroups(coalition.side.RED),GroupsBlue=coalition.getGroups(coalition.side.BLUE),GroupsNeutral=coalition.getGroups(coalition.side.NEUTRAL)} -for CoalitionId,CoalitionData in pairs(CoalitionsData)do -for DCSGroupId,DCSGroup in pairs(CoalitionData)do -if DCSGroup:isExist()then -local DCSGroupName=DCSGroup:getName() -self:I(string.format("Register Group: %s",tostring(DCSGroupName))) -self:AddGroup(DCSGroupName) -for DCSUnitId,DCSUnit in pairs(DCSGroup:getUnits())do -local DCSUnitName=DCSUnit:getName() -self:I(string.format("Register Unit: %s",tostring(DCSUnitName))) -self:AddUnit(DCSUnitName) -end -else -self:E({"Group does not exist: ",DCSGroup}) -end -end -end -return self -end -function DATABASE:_RegisterClients() -for ClientName,ClientTemplate in pairs(self.Templates.ClientsByName)do -self:I(string.format("Register Client: %s",tostring(ClientName))) -local client=self:AddClient(ClientName) -client.SpawnCoord=COORDINATE:New(ClientTemplate.x,ClientTemplate.alt,ClientTemplate.y) -end -return self -end -function DATABASE:_RegisterStatics() -local CoalitionsData={GroupsRed=coalition.getStaticObjects(coalition.side.RED),GroupsBlue=coalition.getStaticObjects(coalition.side.BLUE),GroupsNeutral=coalition.getStaticObjects(coalition.side.NEUTRAL)} -for CoalitionId,CoalitionData in pairs(CoalitionsData)do -for DCSStaticId,DCSStatic in pairs(CoalitionData)do -if DCSStatic:isExist()then -local DCSStaticName=DCSStatic:getName() -self:I(string.format("Register Static: %s",tostring(DCSStaticName))) -self:AddStatic(DCSStaticName) -else -self:E({"Static does not exist: ",DCSStatic}) -end -end -end -return self -end -function DATABASE:_RegisterAirbases() -for DCSAirbaseId,DCSAirbase in pairs(world.getAirbases())do -self:_RegisterAirbase(DCSAirbase) -end -return self -end -function DATABASE:_RegisterAirbase(airbase) -if airbase then -local DCSAirbaseName=airbase:getName() -local airbaseID=airbase:getID() -local airbase=self:AddAirbase(DCSAirbaseName) -local airbaseUID=airbase:GetID(true) -local typename=airbase:GetTypeName() -local category=airbase.category -if category==Airbase.Category.SHIP and typename=="FARP_SINGLE_01"then -category=Airbase.Category.HELIPAD -end -local text=string.format("Register %s: %s (UID=%d), Runways=%d, Parking=%d [",AIRBASE.CategoryName[category],tostring(DCSAirbaseName),airbaseUID,#airbase.runways,airbase.NparkingTotal) -for _,terminalType in pairs(AIRBASE.TerminalType)do -if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType]then -text=text..string.format("%d=%d ",terminalType,airbase.NparkingTerminal[terminalType]) -end -end -text=text.."]" -self:I(text) -end -return self -end -function DATABASE:_EventOnBirth(Event) -self:F({Event}) -if Event.IniDCSUnit then -if Event.IniObjectCategory==Object.Category.STATIC then -self:AddStatic(Event.IniDCSUnitName) -else -if Event.IniObjectCategory==Object.Category.UNIT then -self:AddUnit(Event.IniDCSUnitName) -self:AddGroup(Event.IniDCSGroupName) -local DCSAirbase=Airbase.getByName(Event.IniDCSUnitName) -if DCSAirbase then -self:I(string.format("Adding airbase %s",tostring(Event.IniDCSUnitName))) -self:AddAirbase(Event.IniDCSUnitName) -end -end -end -if Event.IniObjectCategory==Object.Category.UNIT then -Event.IniUnit=self:FindUnit(Event.IniDCSUnitName) -Event.IniGroup=self:FindGroup(Event.IniDCSGroupName) -local client=self.CLIENTS[Event.IniDCSUnitName] -if client then -end -local PlayerName=Event.IniUnit:GetPlayerName() -if PlayerName then -self:I(string.format("Player '%s' joined unit '%s' of group '%s'",tostring(PlayerName),tostring(Event.IniDCSUnitName),tostring(Event.IniDCSGroupName))) -if not client then -client=self:AddClient(Event.IniDCSUnitName) -end -client:AddPlayer(PlayerName) -if not self.PLAYERS[PlayerName]then -self:AddPlayer(Event.IniUnitName,PlayerName) -end -local Settings=SETTINGS:Set(PlayerName) -Settings:SetPlayerMenu(Event.IniUnit) -self:CreateEventPlayerEnterAircraft(Event.IniUnit) -end -end -end -end -function DATABASE:_EventOnDeadOrCrash(Event) -if Event.IniDCSUnit then -local name=Event.IniDCSUnitName -if Event.IniObjectCategory==3 then -if self.STATICS[Event.IniDCSUnitName]then -self:DeleteStatic(Event.IniDCSUnitName) -end -if self.UNITS[Event.IniDCSUnitName]then -self:T("STATIC Event for UNIT "..tostring(Event.IniDCSUnitName)) -local DCSUnit=_DATABASE:FindUnit(Event.IniDCSUnitName) -self:T({DCSUnit}) -if DCSUnit then -return -end -end -else -if Event.IniObjectCategory==1 then -if self.UNITS[Event.IniDCSUnitName]then -self:DeleteUnit(Event.IniDCSUnitName) -end -local client=self.CLIENTS[name] -if client then -client:RemovePlayers() -end -end -end -local airbase=self.AIRBASES[Event.IniDCSUnitName] -if airbase and(airbase:IsHelipad()or airbase:IsShip())then -self:DeleteAirbase(Event.IniDCSUnitName) -end -end -self:AccountDestroys(Event) -end -function DATABASE:_EventOnPlayerEnterUnit(Event) -self:F2({Event}) -if Event.IniDCSUnit then -if Event.IniObjectCategory==1 and Event.IniGroup and Event.IniGroup:IsGround()then -local IsPlayer=Event.IniDCSUnit:getPlayerName() -if IsPlayer then -self:I(string.format("Player '%s' joined GROUND unit '%s' of group '%s'",tostring(Event.IniPlayerName),tostring(Event.IniDCSUnitName),tostring(Event.IniDCSGroupName))) -local client=self.CLIENTS[Event.IniDCSUnitName] -if not client then -client=self:AddClient(Event.IniDCSUnitName) -end -client:AddPlayer(Event.IniPlayerName) -if not self.PLAYERS[Event.IniPlayerName]then -self:AddPlayer(Event.IniUnitName,Event.IniPlayerName) -end -local Settings=SETTINGS:Set(Event.IniPlayerName) -Settings:SetPlayerMenu(Event.IniUnit) -end -end -end -end -function DATABASE:_EventOnPlayerLeaveUnit(Event) -self:F2({Event}) -local function FindPlayerName(UnitName) -local playername=nil -for _name,_unitname in pairs(self.PLAYERS)do -if _unitname==UnitName then -playername=_name -break -end -end -return playername -end -if Event.IniUnit then -if Event.IniObjectCategory==1 then -local PlayerName=Event.IniUnit:GetPlayerName()or FindPlayerName(Event.IniUnitName) -if PlayerName then -self:I(string.format("Player '%s' left unit %s",tostring(PlayerName),tostring(Event.IniUnitName))) -local Settings=SETTINGS:Set(PlayerName) -Settings:RemovePlayerMenu(Event.IniUnit) -self:DeletePlayer(Event.IniUnit,PlayerName) -local client=self.CLIENTS[Event.IniDCSUnitName] -if client then -client:RemovePlayer(PlayerName) -end -end -end -end -end -function DATABASE:ForEach(IteratorFunction,FinalizeFunction,arg,Set) -self:F2(arg) -local function CoRoutine() -local Count=0 -for ObjectID,Object in pairs(Set)do -self:T2(Object) -IteratorFunction(Object,unpack(arg)) -Count=Count+1 -end -return true -end -local co=CoRoutine -local function Schedule() -local status,res=co() -self:T3({status,res}) -if status==false then -error(res) -end -if res==false then -return true -end -if FinalizeFunction then -FinalizeFunction(unpack(arg)) -end -return false -end -Schedule() -return self -end -function DATABASE:ForEachStatic(IteratorFunction,FinalizeFunction,...) -self:F2(arg) -self:ForEach(IteratorFunction,FinalizeFunction,arg,self.STATICS) -return self -end -function DATABASE:ForEachUnit(IteratorFunction,FinalizeFunction,...) -self:F2(arg) -self:ForEach(IteratorFunction,FinalizeFunction,arg,self.UNITS) -return self -end -function DATABASE:ForEachGroup(IteratorFunction,FinalizeFunction,...) -self:F2(arg) -self:ForEach(IteratorFunction,FinalizeFunction,arg,self.GROUPS) -return self -end -function DATABASE:ForEachPlayer(IteratorFunction,FinalizeFunction,...) -self:F2(arg) -self:ForEach(IteratorFunction,FinalizeFunction,arg,self.PLAYERS) -return self -end -function DATABASE:ForEachPlayerJoined(IteratorFunction,FinalizeFunction,...) -self:F2(arg) -self:ForEach(IteratorFunction,FinalizeFunction,arg,self.PLAYERSJOINED) -return self -end -function DATABASE:ForEachPlayerUnit(IteratorFunction,FinalizeFunction,...) -self:F2(arg) -self:ForEach(IteratorFunction,FinalizeFunction,arg,self.PLAYERUNITS) -return self -end -function DATABASE:ForEachClient(IteratorFunction,FinalizeFunction,...) -self:F2(arg) -self:ForEach(IteratorFunction,FinalizeFunction,arg,self.CLIENTS) -return self -end -function DATABASE:ForEachCargo(IteratorFunction,FinalizeFunction,...) -self:F2(arg) -self:ForEach(IteratorFunction,FinalizeFunction,arg,self.CARGOS) -return self -end -function DATABASE:OnEventNewCargo(EventData) -self:F2({EventData}) -if EventData.Cargo then -self:AddCargo(EventData.Cargo) -end -end -function DATABASE:OnEventDeleteCargo(EventData) -self:F2({EventData}) -if EventData.Cargo then -self:DeleteCargo(EventData.Cargo.Name) -end -end -function DATABASE:OnEventNewZone(EventData) -self:F2({EventData}) -if EventData.Zone then -self:AddZone(EventData.Zone.ZoneName,EventData.Zone) -end -end -function DATABASE:OnEventDeleteZone(EventData) -self:F2({EventData}) -if EventData.Zone then -self:DeleteZone(EventData.Zone.ZoneName) -end -end -function DATABASE:GetPlayerSettings(PlayerName) -self:F2({PlayerName}) -return self.PLAYERSETTINGS[PlayerName] -end -function DATABASE:SetPlayerSettings(PlayerName,Settings) -self:F2({PlayerName,Settings}) -self.PLAYERSETTINGS[PlayerName]=Settings -end -function DATABASE:AddOpsGroup(opsgroup) -self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup -end -function DATABASE:GetOpsGroup(groupname) -if type(groupname)=="string"then -else -groupname=groupname:GetName() -end -return self.FLIGHTGROUPS[groupname] -end -function DATABASE:FindOpsGroup(groupname) -if type(groupname)=="string"then -else -groupname=groupname:GetName() -end -return self.FLIGHTGROUPS[groupname] -end -function DATABASE:FindOpsGroupFromUnit(unitname) -local unit=nil -local groupname -if type(unitname)=="string"then -unit=UNIT:FindByName(unitname) -else -unit=unitname -end -if unit then -groupname=unit:GetGroup():GetName() -end -if groupname then -return self.FLIGHTGROUPS[groupname] -else -return nil -end -end -function DATABASE:AddFlightControl(flightcontrol) -self:F2({flightcontrol}) -self.FLIGHTCONTROLS[flightcontrol.airbasename]=flightcontrol -end -function DATABASE:GetFlightControl(airbasename) -return self.FLIGHTCONTROLS[airbasename] -end -function DATABASE:_RegisterTemplates() -self:F2() -self.Navpoints={} -self.UNITS={} -for CoalitionName,coa_data in pairs(env.mission.coalition)do -self:T({CoalitionName=CoalitionName}) -if(CoalitionName=='red'or CoalitionName=='blue'or CoalitionName=='neutrals')and type(coa_data)=='table'then -local CoalitionSide=coalition.side[string.upper(CoalitionName)] -if CoalitionName=="red"then -CoalitionSide=coalition.side.RED -elseif CoalitionName=="blue"then -CoalitionSide=coalition.side.BLUE -else -CoalitionSide=coalition.side.NEUTRAL -end -self.Navpoints[CoalitionName]={} -if coa_data.nav_points then -for nav_ind,nav_data in pairs(coa_data.nav_points)do -if type(nav_data)=='table'then -self.Navpoints[CoalitionName][nav_ind]=UTILS.DeepCopy(nav_data) -self.Navpoints[CoalitionName][nav_ind]['name']=nav_data.callsignStr -self.Navpoints[CoalitionName][nav_ind]['point']={} -self.Navpoints[CoalitionName][nav_ind]['point']['x']=nav_data.x -self.Navpoints[CoalitionName][nav_ind]['point']['y']=0 -self.Navpoints[CoalitionName][nav_ind]['point']['z']=nav_data.y -end -end -end -if coa_data.country then -for cntry_id,cntry_data in pairs(coa_data.country)do -local CountryName=string.upper(cntry_data.name) -local CountryID=cntry_data.id -self.COUNTRY_ID[CountryName]=CountryID -self.COUNTRY_NAME[CountryID]=CountryName -if type(cntry_data)=='table'then -for obj_type_name,obj_type_data in pairs(cntry_data)do -if obj_type_name=="helicopter"or obj_type_name=="ship"or obj_type_name=="plane"or obj_type_name=="vehicle"or obj_type_name=="static"then -local CategoryName=obj_type_name -if((type(obj_type_data)=='table')and obj_type_data.group and(type(obj_type_data.group)=='table')and(#obj_type_data.group>0))then -for group_num,Template in pairs(obj_type_data.group)do -if obj_type_name~="static"and Template and Template.units and type(Template.units)=='table'then -self:_RegisterGroupTemplate(Template,CoalitionSide,_DATABASECategory[string.lower(CategoryName)],CountryID) -else -self:_RegisterStaticTemplate(Template,CoalitionSide,_DATABASECategory[string.lower(CategoryName)],CountryID) -end -end -end -end -end -end -end -end -end -end -return self -end -function DATABASE:AccountHits(Event) -self:F({Event}) -if Event.IniPlayerName~=nil then -self:T("Hitting Something") -if Event.TgtCategory then -self.HITS[Event.TgtUnitName]=self.HITS[Event.TgtUnitName]or{} -local Hit=self.HITS[Event.TgtUnitName] -Hit.Players=Hit.Players or{} -Hit.Players[Event.IniPlayerName]=true -end -end -if Event.WeaponPlayerName~=nil then -self:T("Hitting Scenery") -if Event.TgtCategory then -if Event.WeaponCoalition then -self.HITS[Event.TgtUnitName]=self.HITS[Event.TgtUnitName]or{} -local Hit=self.HITS[Event.TgtUnitName] -Hit.Players=Hit.Players or{} -Hit.Players[Event.WeaponPlayerName]=true -else -end -end -end -end -function DATABASE:AccountDestroys(Event) -self:F({Event}) -local TargetUnit=nil -local TargetGroup=nil -local TargetUnitName="" -local TargetGroupName="" -local TargetPlayerName="" -local TargetCoalition=nil -local TargetCategory=nil -local TargetType=nil -local TargetUnitCoalition=nil -local TargetUnitCategory=nil -local TargetUnitType=nil -if Event.IniDCSUnit then -TargetUnit=Event.IniUnit -TargetUnitName=Event.IniDCSUnitName -TargetGroup=Event.IniDCSGroup -TargetGroupName=Event.IniDCSGroupName -TargetPlayerName=Event.IniPlayerName -TargetCoalition=Event.IniCoalition -TargetCategory=Event.IniCategory -TargetType=Event.IniTypeName -TargetUnitType=TargetType -self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType}) -end -local Destroyed=false -if self.HITS[Event.IniUnitName]then -self.DESTROYS[Event.IniUnitName]=self.DESTROYS[Event.IniUnitName]or{} -self.DESTROYS[Event.IniUnitName]=true -end -end -do -SET_BASE={ -ClassName="SET_BASE", -Filter={}, -Set={}, -List={}, -Index={}, -Database=nil, -CallScheduler=nil, -TimeInterval=nil, -YieldInterval=nil, -} -function SET_BASE:New(Database) -local self=BASE:Inherit(self,FSM:New()) -self.Database=Database -self:SetStartState("Started") -self:AddTransition("*","Added","*") -self:AddTransition("*","Removed","*") -self.YieldInterval=10 -self.TimeInterval=0.001 -self.Set={} -self.Index={} -self.CallScheduler=SCHEDULER:New(self) -self:SetEventPriority(2) -return self -end -function SET_BASE:FilterFunction(ConditionFunction,...) -local condition={} -condition.func=ConditionFunction -condition.arg={} -if arg then -condition.arg=arg -end -if not self.Filter.Functions then self.Filter.Functions={}end -table.insert(self.Filter.Functions,condition) -return self -end -function SET_BASE:_EvalFilterFunctions(Object) -for _,_condition in pairs(self.Filter.Functions or{})do -local condition=_condition -if condition.func(Object,unpack(condition.arg))==false then -return false -end -end -return true -end -function SET_BASE:Clear(TriggerEvent) -for Name,Object in pairs(self.Set)do -self:Remove(Name,not TriggerEvent) -end -return self -end -function SET_BASE:_Find(ObjectName) -local ObjectFound=self.Set[ObjectName] -return ObjectFound -end -function SET_BASE:GetSet() -self:F2() -return self.Set or{} -end -function SET_BASE:GetSetNames() -self:F2() -local Names={} -for Name,Object in pairs(self.Set)do -table.insert(Names,Name) -end -return Names -end -function SET_BASE:GetSetObjects() -self:F2() -local Objects={} -for Name,Object in pairs(self.Set)do -table.insert(Objects,Object) -end -return Objects -end -function SET_BASE:Remove(ObjectName,NoTriggerEvent) -self:F2({ObjectName=ObjectName}) -local TriggerEvent=true -if NoTriggerEvent then -TriggerEvent=false -else -TriggerEvent=true -end -local Object=self.Set[ObjectName] -if Object then -for Index,Key in ipairs(self.Index)do -if Key==ObjectName then -table.remove(self.Index,Index) -self.Set[ObjectName]=nil -break -end -end -if TriggerEvent then -self:Removed(ObjectName,Object) -end -end -end -function SET_BASE:Add(ObjectName,Object) -self:T2({ObjectName=ObjectName,Object=Object}) -if self.Set[ObjectName]then -self:Remove(ObjectName,true) -end -self.Set[ObjectName]=Object -table.insert(self.Index,ObjectName) -self:Added(ObjectName,Object) -return self -end -function SET_BASE:AddObject(Object) -self:F2(Object.ObjectName) -self:T(Object.UnitName) -self:T(Object.ObjectName) -self:Add(Object.ObjectName,Object) -end -function SET_BASE:SortByName() -local function sort(a,b) -return a=Limit then -break -end -end -return true -end -local co=CoRoutine -local function Schedule() -local status,res=co() -self:T3({status,res}) -if status==false then -error(res) -end -if res==false then -return true -end -return false -end -Schedule() -return self -end -function SET_BASE:IsIncludeObject(Object) -self:F3(Object) -return true -end -function SET_BASE:IsInSet(Object) -self:F3(Object) -local outcome=false -local name=Object:GetName() -self:ForEach( -function(object) -if object:GetName()==name then -outcome=true -end -end -) -return outcome -end -function SET_BASE:IsNotInSet(Object) -self:F3(Object) -return not self:IsInSet(Object) -end -function SET_BASE:GetObjectNames() -self:F3() -local ObjectNames="" -for ObjectName,Object in pairs(self.Set)do -ObjectNames=ObjectNames..ObjectName..", " -end -return ObjectNames -end -function SET_BASE:Flush(MasterObject) -self:F3() -local ObjectNames="" -for ObjectName,Object in pairs(self.Set)do -ObjectNames=ObjectNames..ObjectName..", " -end -self:F({MasterObject=MasterObject and MasterObject:GetClassNameAndID(),"Objects in Set:",ObjectNames}) -return ObjectNames -end -end -do -SET_GROUP={ -ClassName="SET_GROUP", -Filter={ -Coalitions=nil, -Categories=nil, -Countries=nil, -GroupPrefixes=nil, -Zones=nil, -Functions=nil, -}, -FilterMeta={ -Coalitions={ -red=coalition.side.RED, -blue=coalition.side.BLUE, -neutral=coalition.side.NEUTRAL, -}, -Categories={ -plane=Group.Category.AIRPLANE, -helicopter=Group.Category.HELICOPTER, -ground=Group.Category.GROUND, -ship=Group.Category.SHIP, -structure=Group.Category.STRUCTURE, -}, -}, -} -function SET_GROUP:New() -local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.GROUPS)) -self:FilterActive(false) -return self -end -function SET_GROUP:GetAliveSet() -self:F2() -local AliveSet=SET_GROUP:New() -for GroupName,GroupObject in pairs(self.Set)do -local GroupObject=GroupObject -if GroupObject then -if GroupObject:IsAlive()then -AliveSet:Add(GroupName,GroupObject) -end -end -end -return AliveSet.Set or{} -end -function SET_GROUP:GetUnitTypeNames() -self:F2() -local MT={} -local UnitTypes={} -local ReportUnitTypes=REPORT:New() -for GroupID,GroupData in pairs(self:GetSet())do -local Units=GroupData:GetUnits() -for UnitID,UnitData in pairs(Units)do -if UnitData:IsAlive()then -local UnitType=UnitData:GetTypeName() -if not UnitTypes[UnitType]then -UnitTypes[UnitType]=1 -else -UnitTypes[UnitType]=UnitTypes[UnitType]+1 -end -end -end -end -for UnitTypeID,UnitType in pairs(UnitTypes)do -ReportUnitTypes:Add(UnitType.." of "..UnitTypeID) -end -return ReportUnitTypes -end -function SET_GROUP:AddGroup(group,DontSetCargoBayLimit) -self:Add(group:GetName(),group) -if not DontSetCargoBayLimit then -for UnitID,UnitData in pairs(group:GetUnits()or{})do -if UnitData and UnitData:IsAlive()then -UnitData:SetCargoBayWeightLimit() -end -end -end -return self -end -function SET_GROUP:AddGroupsByName(AddGroupNames) -local AddGroupNamesArray=(type(AddGroupNames)=="table")and AddGroupNames or{AddGroupNames} -for AddGroupID,AddGroupName in pairs(AddGroupNamesArray)do -self:Add(AddGroupName,GROUP:FindByName(AddGroupName)) -end -return self -end -function SET_GROUP:RemoveGroupsByName(RemoveGroupNames) -local RemoveGroupNamesArray=(type(RemoveGroupNames)=="table")and RemoveGroupNames or{RemoveGroupNames} -for RemoveGroupID,RemoveGroupName in pairs(RemoveGroupNamesArray)do -self:Remove(RemoveGroupName) -end -return self -end -function SET_GROUP:FindGroup(GroupName) -local GroupFound=self.Set[GroupName] -return GroupFound -end -function SET_GROUP:FindNearestGroupFromPointVec2(PointVec2) -self:F2(PointVec2) -local NearestGroup=nil -local ClosestDistance=nil -local Set=self:GetAliveSet() -for ObjectID,ObjectData in pairs(Set)do -if NearestGroup==nil then -NearestGroup=ObjectData -ClosestDistance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate()) -else -local Distance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate()) -if DistanceMaxThreatLevelA2G then -MaxThreatLevelA2G=ThreatLevelA2G -MaxThreatText=ThreatText -end -end -self:F({MaxThreatLevelA2G=MaxThreatLevelA2G,MaxThreatText=MaxThreatText}) -return MaxThreatLevelA2G,MaxThreatText -end -function SET_UNIT:GetCoordinate() -local function GetSetVec3(units) -local x=0 -local y=0 -local z=0 -local n=0 -for _,unit in pairs(units)do -local vec3=nil -if unit and unit:IsAlive()then -vec3=unit:GetVec3() -end -if vec3 then -x=x+vec3.x -y=y+vec3.y -z=z+vec3.z -n=n+1 -end -end -if n>0 then -local Vec3={x=x/n,y=y/n,z=z/n} -return Vec3 -end -return nil -end -local Coordinate=nil -local Vec3=GetSetVec3(self.Set) -if Vec3 then -Coordinate=COORDINATE:NewFromVec3(Vec3) -end -if Coordinate then -local heading=self:GetHeading()or 0 -local velocity=self:GetVelocity()or 0 -Coordinate:SetHeading(heading) -Coordinate:SetVelocity(velocity) -self:I(UTILS.PrintTableToLog(Coordinate)) -end -return Coordinate -end -function SET_UNIT:GetVelocity() -local Coordinate=self:GetFirst():GetCoordinate() -local MaxVelocity=0 -for UnitName,UnitData in pairs(self:GetSet())do -local Unit=UnitData -local Coordinate=Unit:GetCoordinate() -local Velocity=Coordinate:GetVelocity() -if Velocity~=0 then -MaxVelocity=(MaxVelocity5 then -HeadingSet=nil -break -end -end -end -end -return HeadingSet -end -function SET_UNIT:HasRadar(RadarType) -self:F2(RadarType) -local RadarCount=0 -for UnitID,UnitData in pairs(self:GetSet())do -local UnitSensorTest=UnitData -local HasSensors -if RadarType then -HasSensors=UnitSensorTest:HasSensors(Unit.SensorType.RADAR,RadarType) -else -HasSensors=UnitSensorTest:HasSensors(Unit.SensorType.RADAR) -end -self:T3(HasSensors) -if HasSensors then -RadarCount=RadarCount+1 -end -end -return RadarCount -end -function SET_UNIT:HasSEAD() -self:F2() -local SEADCount=0 -for UnitID,UnitData in pairs(self:GetSet())do -local UnitSEAD=UnitData -if UnitSEAD:IsAlive()then -local UnitSEADAttributes=UnitSEAD:GetDesc().attributes -local HasSEAD=UnitSEAD:HasSEAD() -self:T3(HasSEAD) -if HasSEAD then -SEADCount=SEADCount+1 -end -end -end -return SEADCount -end -function SET_UNIT:HasGroundUnits() -self:F2() -local GroundUnitCount=0 -for UnitID,UnitData in pairs(self:GetSet())do -local UnitTest=UnitData -if UnitTest:IsGround()then -GroundUnitCount=GroundUnitCount+1 -end -end -return GroundUnitCount -end -function SET_UNIT:HasAirUnits() -self:F2() -local AirUnitCount=0 -for UnitID,UnitData in pairs(self:GetSet())do -local UnitTest=UnitData -if UnitTest:IsAir()then -AirUnitCount=AirUnitCount+1 -end -end -return AirUnitCount -end -function SET_UNIT:HasFriendlyUnits(FriendlyCoalition) -self:F2() -local FriendlyUnitCount=0 -for UnitID,UnitData in pairs(self:GetSet())do -local UnitTest=UnitData -if UnitTest:IsFriendly(FriendlyCoalition)then -FriendlyUnitCount=FriendlyUnitCount+1 -end -end -return FriendlyUnitCount -end -function SET_UNIT:IsIncludeObject(MUnit) -self:F2({MUnit}) -local MUnitInclude=false -if MUnit:IsAlive()~=nil then -MUnitInclude=true -if self.Filter.Active~=nil then -local MUnitActive=false -if self.Filter.Active==false or(self.Filter.Active==true and MUnit:IsActive()==true)then -MUnitActive=true -end -MUnitInclude=MUnitInclude and MUnitActive -end -if self.Filter.Coalitions and MUnitInclude then -local MUnitCoalition=false -for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do -self:F({"Coalition:",MUnit:GetCoalition(),self.FilterMeta.Coalitions[CoalitionName],CoalitionName}) -if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MUnit:GetCoalition()then -MUnitCoalition=true -end -end -MUnitInclude=MUnitInclude and MUnitCoalition -end -if self.Filter.Categories and MUnitInclude then -local MUnitCategory=false -for CategoryID,CategoryName in pairs(self.Filter.Categories)do -self:T3({"Category:",MUnit:GetDesc().category,self.FilterMeta.Categories[CategoryName],CategoryName}) -if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MUnit:GetDesc().category then -MUnitCategory=true -end -end -MUnitInclude=MUnitInclude and MUnitCategory -end -if self.Filter.Types and MUnitInclude then -local MUnitType=false -for TypeID,TypeName in pairs(self.Filter.Types)do -self:T3({"Type:",MUnit:GetTypeName(),TypeName}) -if TypeName==MUnit:GetTypeName()then -MUnitType=true -end -end -MUnitInclude=MUnitInclude and MUnitType -end -if self.Filter.Countries and MUnitInclude then -local MUnitCountry=false -for CountryID,CountryName in pairs(self.Filter.Countries)do -self:T3({"Country:",MUnit:GetCountry(),CountryName}) -if country.id[CountryName]==MUnit:GetCountry()then -MUnitCountry=true -end -end -MUnitInclude=MUnitInclude and MUnitCountry -end -if self.Filter.UnitPrefixes and MUnitInclude then -local MUnitPrefix=false -for UnitPrefixId,UnitPrefix in pairs(self.Filter.UnitPrefixes)do -self:T3({"Prefix:",string.find(MUnit:GetName(),UnitPrefix,1),UnitPrefix}) -if string.find(MUnit:GetName(),UnitPrefix,1)then -MUnitPrefix=true -end -end -MUnitInclude=MUnitInclude and MUnitPrefix -end -if self.Filter.RadarTypes and MUnitInclude then -local MUnitRadar=false -for RadarTypeID,RadarType in pairs(self.Filter.RadarTypes)do -self:T3({"Radar:",RadarType}) -if MUnit:HasSensors(Unit.SensorType.RADAR,RadarType)==true then -if MUnit:GetRadar()==true then -self:T3("RADAR Found") -end -MUnitRadar=true -end -end -MUnitInclude=MUnitInclude and MUnitRadar -end -if self.Filter.SEAD and MUnitInclude then -local MUnitSEAD=false -if MUnit:HasSEAD()==true then -self:T3("SEAD Found") -MUnitSEAD=true -end -MUnitInclude=MUnitInclude and MUnitSEAD -end -end -if self.Filter.Zones and MUnitInclude then -local MGroupZone=false -for ZoneName,Zone in pairs(self.Filter.Zones)do -self:T3("Zone:",ZoneName) -if MUnit:IsInZone(Zone)then -MGroupZone=true -end -end -MUnitInclude=MUnitInclude and MGroupZone -end -if self.Filter.Functions and MUnitInclude then -local MUnitFunc=self:_EvalFilterFunctions(MUnit) -MUnitInclude=MUnitInclude and MUnitFunc -end -self:T2(MUnitInclude) -return MUnitInclude -end -function SET_UNIT:GetTypeNames(Delimiter) -Delimiter=Delimiter or", " -local TypeReport=REPORT:New() -local Types={} -for UnitName,UnitData in pairs(self:GetSet())do -local Unit=UnitData -local UnitTypeName=Unit:GetTypeName() -if not Types[UnitTypeName]then -Types[UnitTypeName]=UnitTypeName -TypeReport:Add(UnitTypeName) -end -end -return TypeReport:Text(Delimiter) -end -function SET_UNIT:SetCargoBayWeightLimit() -local Set=self:GetSet() -for UnitID,UnitData in pairs(Set)do -UnitData:SetCargoBayWeightLimit() -end -end -end -do -SET_STATIC={ -ClassName="SET_STATIC", -Statics={}, -Filter={ -Coalitions=nil, -Categories=nil, -Types=nil, -Countries=nil, -StaticPrefixes=nil, -Zones=nil, -}, -FilterMeta={ -Coalitions={ -red=coalition.side.RED, -blue=coalition.side.BLUE, -neutral=coalition.side.NEUTRAL, -}, -Categories={ -plane=Unit.Category.AIRPLANE, -helicopter=Unit.Category.HELICOPTER, -ground=Unit.Category.GROUND_STATIC, -ship=Unit.Category.SHIP, -structure=Unit.Category.STRUCTURE, -}, -}, -} -function SET_STATIC:New() -local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.STATICS)) -return self -end -function SET_STATIC:AddStatic(AddStatic) -self:F2(AddStatic:GetName()) -self:Add(AddStatic:GetName(),AddStatic) -return self -end -function SET_STATIC:AddStaticsByName(AddStaticNames) -local AddStaticNamesArray=(type(AddStaticNames)=="table")and AddStaticNames or{AddStaticNames} -self:T(AddStaticNamesArray) -for AddStaticID,AddStaticName in pairs(AddStaticNamesArray)do -self:Add(AddStaticName,STATIC:FindByName(AddStaticName)) -end -return self -end -function SET_STATIC:RemoveStaticsByName(RemoveStaticNames) -local RemoveStaticNamesArray=(type(RemoveStaticNames)=="table")and RemoveStaticNames or{RemoveStaticNames} -for RemoveStaticID,RemoveStaticName in pairs(RemoveStaticNamesArray)do -self:Remove(RemoveStaticName) -end -return self -end -function SET_STATIC:FindStatic(StaticName) -local StaticFound=self.Set[StaticName] -return StaticFound -end -function SET_STATIC:FilterCoalitions(Coalitions) -if not self.Filter.Coalitions then -self.Filter.Coalitions={} -end -if type(Coalitions)~="table"then -Coalitions={Coalitions} -end -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end -function SET_STATIC:FilterZones(Zones) -if not self.Filter.Zones then -self.Filter.Zones={} -end -local zones={} -if Zones.ClassName and Zones.ClassName=="SET_ZONE"then -zones=Zones.Set -elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then -self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") -return self -else -zones=Zones -end -for _,Zone in pairs(zones)do -local zonename=Zone:GetName() -self.Filter.Zones[zonename]=Zone -end -return self -end -function SET_STATIC:FilterCategories(Categories) -if not self.Filter.Categories then -self.Filter.Categories={} -end -if type(Categories)~="table"then -Categories={Categories} -end -for CategoryID,Category in pairs(Categories)do -self.Filter.Categories[Category]=Category -end -return self -end -function SET_STATIC:FilterTypes(Types) -if not self.Filter.Types then -self.Filter.Types={} -end -if type(Types)~="table"then -Types={Types} -end -for TypeID,Type in pairs(Types)do -self.Filter.Types[Type]=Type -end -return self -end -function SET_STATIC:FilterCountries(Countries) -if not self.Filter.Countries then -self.Filter.Countries={} -end -if type(Countries)~="table"then -Countries={Countries} -end -for CountryID,Country in pairs(Countries)do -self.Filter.Countries[Country]=Country -end -return self -end -function SET_STATIC:FilterPrefixes(Prefixes) -if not self.Filter.StaticPrefixes then -self.Filter.StaticPrefixes={} -end -if type(Prefixes)~="table"then -Prefixes={Prefixes} -end -for PrefixID,Prefix in pairs(Prefixes)do -self.Filter.StaticPrefixes[Prefix]=Prefix -end -return self -end -function SET_STATIC:FilterStart() -if _DATABASE then -self:_FilterStart() -self:HandleEvent(EVENTS.Birth,self._EventOnBirth) -self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) -self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) -end -return self -end -function SET_STATIC:CountAlive() -local Set=self:GetSet() -local CountU=0 -for UnitID,UnitData in pairs(Set)do -if UnitData and UnitData:IsAlive()then -CountU=CountU+1 -end -end -return CountU -end -function SET_STATIC:AddInDatabase(Event) -self:F3({Event}) -if Event.IniObjectCategory==Object.Category.STATIC then -if not self.Database[Event.IniDCSUnitName]then -self.Database[Event.IniDCSUnitName]=STATIC:Register(Event.IniDCSUnitName) -self:T3(self.Database[Event.IniDCSUnitName]) -end -end -return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName] -end -function SET_STATIC:FindInDatabase(Event) -self:F2({Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName],Event}) -return Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName] -end -do -function SET_STATIC:IsPartiallyInZone(Zone) -local IsPartiallyInZone=false -local function EvaluateZone(ZoneStatic) -local ZoneStaticName=ZoneStatic:GetName() -if self:FindStatic(ZoneStaticName)then -IsPartiallyInZone=true -return false -end -return true -end -return IsPartiallyInZone -end -function SET_STATIC:IsNotInZone(Zone) -local IsNotInZone=true -local function EvaluateZone(ZoneStatic) -local ZoneStaticName=ZoneStatic:GetName() -if self:FindStatic(ZoneStaticName)then -IsNotInZone=false -return false -end -return true -end -Zone:Search(EvaluateZone) -return IsNotInZone -end -function SET_STATIC:ForEachStaticInZone(IteratorFunction,...) -self:F2(arg) -self:ForEach(IteratorFunction,arg,self:GetSet()) -return self -end -end -function SET_STATIC:ForEachStatic(IteratorFunction,...) -self:F2(arg) -self:ForEach(IteratorFunction,arg,self:GetSet()) -return self -end -function SET_STATIC:ForEachStaticCompletelyInZone(ZoneObject,IteratorFunction,...) -self:F2(arg) -self:ForEach(IteratorFunction,arg,self:GetSet(), -function(ZoneObject,StaticObject) -if StaticObject:IsInZone(ZoneObject)then -return true -else -return false -end -end,{ZoneObject}) -return self -end -function SET_STATIC:ForEachStaticNotInZone(ZoneObject,IteratorFunction,...) -self:F2(arg) -self:ForEach(IteratorFunction,arg,self:GetSet(), -function(ZoneObject,StaticObject) -if StaticObject:IsNotInZone(ZoneObject)then -return true -else -return false -end -end,{ZoneObject}) -return self -end -function SET_STATIC:GetStaticTypes() -self:F2() -local MT={} -local StaticTypes={} -for StaticID,StaticData in pairs(self:GetSet())do -local TextStatic=StaticData -if TextStatic:IsAlive()then -local StaticType=TextStatic:GetTypeName() -if not StaticTypes[StaticType]then -StaticTypes[StaticType]=1 -else -StaticTypes[StaticType]=StaticTypes[StaticType]+1 -end -end -end -for StaticTypeID,StaticType in pairs(StaticTypes)do -MT[#MT+1]=StaticType.." of "..StaticTypeID -end -return StaticTypes -end -function SET_STATIC:GetStaticTypesText() -self:F2() -local MT={} -local StaticTypes=self:GetStaticTypes() -for StaticTypeID,StaticType in pairs(StaticTypes)do -MT[#MT+1]=StaticType.." of "..StaticTypeID -end -return table.concat(MT,", ") -end -function SET_STATIC:GetCoordinate() -local Coordinate=self:GetFirst():GetCoordinate() -local x1=Coordinate.x -local x2=Coordinate.x -local y1=Coordinate.y -local y2=Coordinate.y -local z1=Coordinate.z -local z2=Coordinate.z -local MaxVelocity=0 -local AvgHeading=nil -local MovingCount=0 -for StaticName,StaticData in pairs(self:GetSet())do -local Static=StaticData -local Coordinate=Static:GetCoordinate() -x1=(Coordinate.xx2)and Coordinate.x or x2 -y1=(Coordinate.yy2)and Coordinate.y or y2 -z1=(Coordinate.yz2)and Coordinate.z or z2 -local Velocity=Coordinate:GetVelocity() -if Velocity~=0 then -MaxVelocity=(MaxVelocity5 then -HeadingSet=nil -break -end -end -end -end -return HeadingSet -end -function SET_STATIC:CalculateThreatLevelA2G() -local MaxThreatLevelA2G=0 -local MaxThreatText="" -for StaticName,StaticData in pairs(self:GetSet())do -local ThreatStatic=StaticData -local ThreatLevelA2G,ThreatText=ThreatStatic:GetThreatLevel() -if ThreatLevelA2G>MaxThreatLevelA2G then -MaxThreatLevelA2G=ThreatLevelA2G -MaxThreatText=ThreatText -end -end -self:F({MaxThreatLevelA2G=MaxThreatLevelA2G,MaxThreatText=MaxThreatText}) -return MaxThreatLevelA2G,MaxThreatText -end -function SET_STATIC:IsIncludeObject(MStatic) -self:F2(MStatic) -local MStaticInclude=true -if self.Filter.Coalitions then -local MStaticCoalition=false -for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do -self:T3({"Coalition:",MStatic:GetCoalition(),self.FilterMeta.Coalitions[CoalitionName],CoalitionName}) -if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MStatic:GetCoalition()then -MStaticCoalition=true -end -end -MStaticInclude=MStaticInclude and MStaticCoalition -end -if self.Filter.Categories then -local MStaticCategory=false -for CategoryID,CategoryName in pairs(self.Filter.Categories)do -self:T3({"Category:",MStatic:GetDesc().category,self.FilterMeta.Categories[CategoryName],CategoryName}) -if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MStatic:GetDesc().category then -MStaticCategory=true -end -end -MStaticInclude=MStaticInclude and MStaticCategory -end -if self.Filter.Types then -local MStaticType=false -for TypeID,TypeName in pairs(self.Filter.Types)do -self:T3({"Type:",MStatic:GetTypeName(),TypeName}) -if TypeName==MStatic:GetTypeName()then -MStaticType=true -end -end -MStaticInclude=MStaticInclude and MStaticType -end -if self.Filter.Countries then -local MStaticCountry=false -for CountryID,CountryName in pairs(self.Filter.Countries)do -self:T3({"Country:",MStatic:GetCountry(),CountryName}) -if country.id[CountryName]==MStatic:GetCountry()then -MStaticCountry=true -end -end -MStaticInclude=MStaticInclude and MStaticCountry -end -if self.Filter.StaticPrefixes then -local MStaticPrefix=false -for StaticPrefixId,StaticPrefix in pairs(self.Filter.StaticPrefixes)do -self:T3({"Prefix:",string.find(MStatic:GetName(),StaticPrefix,1),StaticPrefix}) -if string.find(MStatic:GetName(),StaticPrefix,1)then -MStaticPrefix=true -end -end -MStaticInclude=MStaticInclude and MStaticPrefix -end -if self.Filter.Zones then -local MStaticZone=false -for ZoneName,Zone in pairs(self.Filter.Zones)do -self:T3("Zone:",ZoneName) -if MStatic and MStatic:IsInZone(Zone)then -MStaticZone=true -end -end -MStaticInclude=MStaticInclude and MStaticZone -end -self:T2(MStaticInclude) -return MStaticInclude -end -function SET_STATIC:GetTypeNames(Delimiter) -Delimiter=Delimiter or", " -local TypeReport=REPORT:New() -local Types={} -for StaticName,StaticData in pairs(self:GetSet())do -local Static=StaticData -local StaticTypeName=Static:GetTypeName() -if not Types[StaticTypeName]then -Types[StaticTypeName]=StaticTypeName -TypeReport:Add(StaticTypeName) -end -end -return TypeReport:Text(Delimiter) -end -function SET_STATIC:GetClosestStatic(Coordinate,Coalitions) -local Set=self:GetSet() -local dmin=math.huge -local gmin=nil -for GroupID,GroupData in pairs(Set)do -local group=GroupData -if group and group:IsAlive()and(Coalitions==nil or UTILS.IsAnyInTable(Coalitions,group:GetCoalition()))then -local coord=group:GetCoord() -local d=UTILS.VecDist3D(Coordinate,coord) -if d1 then -x=x/count -y=y/count -z=z/count -end -local coord=COORDINATE:New(x,y,z) -return coord -end -function SET_ZONE:IsIncludeObject(MZone) -self:F2(MZone) -local MZoneInclude=true -if MZone then -local MZoneName=MZone:GetName() -if self.Filter.Prefixes then -local MZonePrefix=false -for ZonePrefixId,ZonePrefix in pairs(self.Filter.Prefixes)do -self:T2({"Prefix:",string.find(MZoneName,ZonePrefix,1),ZonePrefix}) -if string.find(MZoneName,ZonePrefix,1)then -MZonePrefix=true -end -end -self:T({"Evaluated Prefix",MZonePrefix}) -MZoneInclude=MZoneInclude and MZonePrefix -end -end -self:T2(MZoneInclude) -return MZoneInclude -end -function SET_ZONE:OnEventNewZone(EventData) -self:F({"New Zone",EventData}) -if EventData.Zone then -if EventData.Zone and self:IsIncludeObject(EventData.Zone)then -self:Add(EventData.Zone.ZoneName,EventData.Zone) -end -end -end -function SET_ZONE:OnEventDeleteZone(EventData) -self:F3({EventData}) -if EventData.Zone then -local Zone=_DATABASE:FindZone(EventData.Zone.ZoneName) -if Zone and Zone.ZoneName then -self:F({ZoneNoDestroy=Zone.NoDestroy}) -if Zone.NoDestroy then -else -self:Remove(Zone.ZoneName) -end -end -end -end -function SET_ZONE:IsCoordinateInZone(Coordinate) -for _,Zone in pairs(self:GetSet())do -local Zone=Zone -if Zone:IsCoordinateInZone(Coordinate)then -return Zone -end -end -return nil -end -function SET_ZONE:GetClosestZone(Coordinate) -local dmin=math.huge -local zmin=nil -for _,Zone in pairs(self:GetSet())do -local Zone=Zone -local d=Zone:Get2DDistance(Coordinate) -if dx2)and Coordinate.x or x2 -y1=(Coordinate.yy2)and Coordinate.y or y2 -z1=(Coordinate.yz2)and Coordinate.z or z2 -end -Coordinate.x=(x2-x1)/2+x1 -Coordinate.y=(y2-y1)/2+y1 -Coordinate.z=(z2-z1)/2+z1 -self:F({Coordinate=Coordinate}) -return Coordinate -end -function SET_SCENERY:IsIncludeObject(MScenery) -self:T(MScenery.SceneryName) -local MSceneryInclude=true -if MScenery then -local MSceneryName=MScenery:GetName() -if self.Filter.Prefixes then -local MSceneryPrefix=false -for ZonePrefixId,ZonePrefix in pairs(self.Filter.Prefixes)do -self:T({"Prefix:",string.find(MSceneryName,ZonePrefix,1),ZonePrefix}) -if string.find(MSceneryName,ZonePrefix,1)then -MSceneryPrefix=true -end -end -self:T({"Evaluated Prefix",MSceneryPrefix}) -MSceneryInclude=MSceneryInclude and MSceneryPrefix -end -if self.Filter.Zones then -local MSceneryZone=false -for ZoneName,Zone in pairs(self.Filter.Zones)do -local coord=MScenery:GetCoordinate() -if coord and Zone:IsCoordinateInZone(coord)then -MSceneryZone=true -end -self:T({"Evaluated Zone",MSceneryZone}) -end -MSceneryInclude=MSceneryInclude and MSceneryZone -end -if self.Filter.SceneryRoles then -local MSceneryRole=false -local Role=MScenery:GetProperty("ROLE")or"none" -for ZoneRoleId,ZoneRole in pairs(self.Filter.SceneryRoles)do -self:T({"Role:",ZoneRole,Role}) -if ZoneRole==Role then -MSceneryRole=true -end -end -self:T({"Evaluated Role ",MSceneryRole}) -MSceneryInclude=MSceneryInclude and MSceneryRole -end -end -self:T2(MSceneryInclude) -return MSceneryInclude -end -function SET_SCENERY:FilterOnce() -for ObjectName,Object in pairs(self:GetSet())do -self:T(ObjectName) -if self:IsIncludeObject(Object)then -self:Add(ObjectName,Object) -else -self:Remove(ObjectName,true) -end -end -return self -end -function SET_SCENERY:GetLife0() -local life0=0 -self:ForEachScenery( -function(obj) -local Obj=obj -life0=life0+Obj:GetLife0() -end -) -return life0 -end -function SET_SCENERY:GetLife() -local life=0 -self:ForEachScenery( -function(obj) -local Obj=obj -life=life+Obj:GetLife() -end -) -return life -end -function SET_SCENERY:GetRelativeLife() -local life=self:GetLife() -local life0=self:GetLife0() -self:T2(string.format("Set Lifepoints: %d life0 | %d life",life0,life)) -local rlife=math.floor((life/life0)*100) -return rlife -end -end -do -COORDINATE={ -ClassName="COORDINATE", -} -COORDINATE.WaypointAltType={ -BARO="BARO", -RADIO="RADIO", -} -COORDINATE.WaypointAction={ -TurningPoint="Turning Point", -FlyoverPoint="Fly Over Point", -FromParkingArea="From Parking Area", -FromParkingAreaHot="From Parking Area Hot", -FromGroundAreaHot="From Ground Area Hot", -FromGroundArea="From Ground Area", -FromRunway="From Runway", -Landing="Landing", -LandingReFuAr="LandingReFuAr", -} -COORDINATE.WaypointType={ -TakeOffParking="TakeOffParking", -TakeOffParkingHot="TakeOffParkingHot", -TakeOff="TakeOffParkingHot", -TakeOffGroundHot="TakeOffGroundHot", -TakeOffGround="TakeOffGround", -TurningPoint="Turning Point", -Land="Land", -LandingReFuAr="LandingReFuAr", -} -function COORDINATE:New(x,y,z) -local self=BASE:Inherit(self,BASE:New()) -self.x=x -self.y=y -self.z=z -return self -end -function COORDINATE:NewFromCoordinate(Coordinate) -local self=BASE:Inherit(self,BASE:New()) -self.x=Coordinate.x -self.y=Coordinate.y -self.z=Coordinate.z -return self -end -function COORDINATE:NewFromVec2(Vec2,LandHeightAdd) -local LandHeight=land.getHeight(Vec2) -LandHeightAdd=LandHeightAdd or 0 -LandHeight=LandHeight+LandHeightAdd -local self=self:New(Vec2.x,LandHeight,Vec2.y) -return self -end -function COORDINATE:NewFromVec3(Vec3) -local self=self:New(Vec3.x,Vec3.y,Vec3.z) -self:F2(self) -return self -end -function COORDINATE:NewFromWaypoint(Waypoint) -local self=self:New(Waypoint.x,Waypoint.alt,Waypoint.y) -return self -end -function COORDINATE:GetCoordinate() -return self -end -function COORDINATE:GetVec3() -return{x=self.x,y=self.y,z=self.z} -end -function COORDINATE:GetVec2() -return{x=self.x,y=self.z} -end -function COORDINATE:UpdateFromVec3(Vec3) -self.x=Vec3.x -self.y=Vec3.y -self.z=Vec3.z -return self -end -function COORDINATE:UpdateFromCoordinate(Coordinate) -self.x=Coordinate.x -self.y=Coordinate.y -self.z=Coordinate.z -return self -end -function COORDINATE:UpdateFromVec2(Vec2) -self.x=Vec2.x -self.z=Vec2.y -return self -end -function COORDINATE:GetMagneticDeclination(Month,Year) -local decl=UTILS.GetMagneticDeclination() -if require then -local magvar=require('magvar') -if magvar then -local date,year,month,day=UTILS.GetDCSMissionDate() -magvar.init(Month or month,Year or year) -local lat,lon=self:GetLLDDM() -decl=magvar.get_mag_decl(lat,lon) -if decl then -decl=math.deg(decl) -end -end -else -self:T("The require package is not available. Using constant value for magnetic declination") -end -return decl -end -function COORDINATE:NewFromLLDD(latitude,longitude,altitude) -local vec3=coord.LLtoLO(latitude,longitude) -local _coord=self:NewFromVec3(vec3) -if altitude==nil then -_coord.y=self:GetLandHeight() -else -_coord.y=altitude -end -return _coord -end -function COORDINATE:IsAtCoordinate2D(Coordinate,Precision) -self:F({Coordinate=Coordinate:GetVec2()}) -self:F({self=self:GetVec2()}) -local x=Coordinate.x -local z=Coordinate.z -return x-Precision<=self.x and x+Precision>=self.x and z-Precision<=self.z and z+Precision>=self.z -end -function COORDINATE:ScanObjects(radius,scanunits,scanstatics,scanscenery) -self:F(string.format("Scanning in radius %.1f m.",radius or 100)) -local SphereSearch={ -id=world.VolumeType.SPHERE, -params={ -point=self:GetVec3(), -radius=radius, -} -} -radius=radius or 100 -if scanunits==nil then -scanunits=true -end -if scanstatics==nil then -scanstatics=true -end -if scanscenery==nil then -scanscenery=false -end -local scanobjects={} -if scanunits then -table.insert(scanobjects,Object.Category.UNIT) -end -if scanstatics then -table.insert(scanobjects,Object.Category.STATIC) -end -if scanscenery then -table.insert(scanobjects,Object.Category.SCENERY) -end -local Units={} -local Statics={} -local Scenery={} -local gotstatics=false -local gotunits=false -local gotscenery=false -local function EvaluateZone(ZoneObject) -if ZoneObject then -local ObjectCategory=Object.getCategory(ZoneObject) -if ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()then -table.insert(Units,UNIT:Find(ZoneObject)) -gotunits=true -elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist()then -table.insert(Statics,ZoneObject) -gotstatics=true -elseif ObjectCategory==Object.Category.SCENERY then -table.insert(Scenery,ZoneObject) -gotscenery=true -end -end -return true -end -world.searchObjects(scanobjects,SphereSearch,EvaluateZone) -for _,unit in pairs(Units)do -self:T(string.format("Scan found unit %s",unit:GetName())) -end -for _,static in pairs(Statics)do -self:T(string.format("Scan found static %s",static:getName())) -_DATABASE:AddStatic(static:getName()) -end -for _,scenery in pairs(Scenery)do -self:T(string.format("Scan found scenery %s typename=%s",scenery:getName(),scenery:getTypeName())) -end -return gotunits,gotstatics,gotscenery,Units,Statics,Scenery -end -function COORDINATE:ScanUnits(radius) -local _,_,_,units=self:ScanObjects(radius,true,false,false) -local set=SET_UNIT:New() -for _,unit in pairs(units)do -set:AddUnit(unit) -end -return set -end -function COORDINATE:ScanStatics(radius) -local _,_,_,_,statics=self:ScanObjects(radius,false,true,false) -local set=SET_STATIC:New() -for _,stat in pairs(statics)do -set:AddStatic(STATIC:Find(stat)) -end -return set -end -function COORDINATE:FindClosestStatic(radius) -local units=self:ScanStatics(radius) -local umin=nil -local dmin=math.huge -for _,_unit in pairs(units.Set)do -local unit=_unit -local coordinate=unit:GetCoordinate() -local d=self:Get2DDistance(coordinate) -if d1 then -Radials=2-Radials -end -local RadialMultiplier -if InnerRadius and InnerRadius<=OuterRadius then -RadialMultiplier=(OuterRadius-InnerRadius)*Radials+InnerRadius -else -RadialMultiplier=OuterRadius*Radials -end -local RandomVec2 -if OuterRadius>0 then -RandomVec2={x=math.cos(Theta)*RadialMultiplier+self.x,y=math.sin(Theta)*RadialMultiplier+self.z} -else -RandomVec2={x=self.x,y=self.z} -end -return RandomVec2 -end -function COORDINATE:GetRandomCoordinateInRadius(OuterRadius,InnerRadius) -self:F2({OuterRadius,InnerRadius}) -local coord=COORDINATE:NewFromVec2(self:GetRandomVec2InRadius(OuterRadius,InnerRadius)) -return coord -end -function COORDINATE:GetRandomVec3InRadius(OuterRadius,InnerRadius) -local RandomVec2=self:GetRandomVec2InRadius(OuterRadius,InnerRadius) -local y=self.y+math.random(InnerRadius,OuterRadius) -local RandomVec3={x=RandomVec2.x,y=y,z=RandomVec2.y} -return RandomVec3 -end -function COORDINATE:GetLandHeight() -local Vec2={x=self.x,y=self.z} -return land.getHeight(Vec2) -end -function COORDINATE:SetHeading(Heading) -self.Heading=Heading -end -function COORDINATE:GetHeading() -return self.Heading -end -function COORDINATE:SetVelocity(Velocity) -self.Velocity=Velocity -end -function COORDINATE:GetVelocity() -local Velocity=self.Velocity -return Velocity or 0 -end -function COORDINATE:GetName() -local name=self:ToStringMGRS() -return name -end -function COORDINATE:GetMovingText(Settings) -return self:GetVelocityText(Settings)..", "..self:GetHeadingText(Settings) -end -function COORDINATE:GetDirectionVec3(TargetCoordinate) -if TargetCoordinate then -return{x=TargetCoordinate.x-self.x,y=TargetCoordinate.y-self.y,z=TargetCoordinate.z-self.z} -else -return{x=0,y=0,z=0} -end -end -function COORDINATE:GetNorthCorrectionRadians() -local TargetVec3=self:GetVec3() -local lat,lon=coord.LOtoLL(TargetVec3) -local north_posit=coord.LLtoLO(lat+1,lon) -return math.atan2(north_posit.z-TargetVec3.z,north_posit.x-TargetVec3.x) -end -function COORDINATE:GetAngleRadians(DirectionVec3) -local DirectionRadians=math.atan2(DirectionVec3.z,DirectionVec3.x) -if DirectionRadians<0 then -DirectionRadians=DirectionRadians+2*math.pi -end -return DirectionRadians -end -function COORDINATE:GetAngleDegrees(DirectionVec3) -local AngleRadians=self:GetAngleRadians(DirectionVec3) -local Angle=UTILS.ToDegree(AngleRadians) -return Angle -end -function COORDINATE:GetIntermediateCoordinate(ToCoordinate,Fraction) -local f=Fraction or 0.5 -local vec=UTILS.VecSubstract(ToCoordinate,self) -if f>1 then -local norm=UTILS.VecNorm(vec) -f=Fraction/norm -end -vec.x=f*vec.x -vec.y=f*vec.y -vec.z=f*vec.z -vec=UTILS.VecAdd(self,vec) -local coord=COORDINATE:New(vec.x,vec.y,vec.z) -return coord -end -function COORDINATE:Get2DDistance(TargetCoordinate) -if not TargetCoordinate then return 1000000 end -local a={x=TargetCoordinate.x-self.x,y=0,z=TargetCoordinate.z-self.z} -local norm=UTILS.VecNorm(a) -return norm -end -function COORDINATE:GetTemperature(height) -self:F2(height) -local y=height or self.y -local point={x=self.x,y=height or self.y,z=self.z} -local T,P=atmosphere.getTemperatureAndPressure(point) -return T-273.15 -end -function COORDINATE:GetTemperatureText(height,Settings) -local DegreesCelcius=self:GetTemperature(height) -local Settings=Settings or _SETTINGS -if DegreesCelcius then -if Settings:IsMetric()then -return string.format(" %-2.2f °C",DegreesCelcius) -else -return string.format(" %-2.2f °F",UTILS.CelsiusToFahrenheit(DegreesCelcius)) -end -else -return" no temperature" -end -return nil -end -function COORDINATE:GetPressure(height) -local point={x=self.x,y=height or self.y,z=self.z} -local T,P=atmosphere.getTemperatureAndPressure(point) -return P/100 -end -function COORDINATE:GetPressureText(height,Settings) -local Pressure_hPa=self:GetPressure(height) -local Pressure_mmHg=Pressure_hPa*0.7500615613030 -local Pressure_inHg=Pressure_hPa*0.0295299830714 -local Settings=Settings or _SETTINGS -if Pressure_hPa then -if Settings:IsMetric()then -return string.format(" %4.1f hPa (%3.1f mmHg)",Pressure_hPa,Pressure_mmHg) -else -return string.format(" %4.1f hPa (%3.2f inHg)",Pressure_hPa,Pressure_inHg) -end -else -return" no pressure" -end -return nil -end -function COORDINATE:HeadingTo(ToCoordinate) -local dz=ToCoordinate.z-self.z -local dx=ToCoordinate.x-self.x -local heading=math.deg(math.atan2(dz,dx)) -if heading<0 then -heading=360+heading -end -return heading -end -function COORDINATE:GetWindVec3(height,turbulence) -local landheight=self:GetLandHeight()+0.1 -local point={x=self.x,y=math.max(height or self.y,landheight),z=self.z} -local wind=nil -if turbulence then -wind=atmosphere.getWindWithTurbulence(point) -else -wind=atmosphere.getWind(point) -end -return wind -end -function COORDINATE:GetWind(height,turbulence) -local wind=self:GetWindVec3(height,turbulence) -local direction=UTILS.VecHdg(wind) -if direction>180 then -direction=direction-180 -else -direction=direction+180 -end -local strength=UTILS.VecNorm(wind) -return direction,strength -end -function COORDINATE:GetWindWithTurbulenceVec3(height) -local landheight=self:GetLandHeight()+0.1 -local point={x=self.x,y=math.max(height or self.y,landheight),z=self.z} -local vec3=atmosphere.getWindWithTurbulence(point) -return vec3 -end -function COORDINATE:GetWindText(height,Settings) -local Direction,Strength=self:GetWind(height) -local Settings=Settings or _SETTINGS -if Direction and Strength then -if Settings:IsMetric()then -return string.format(" %d ° at %3.2f mps",Direction,UTILS.MpsToKmph(Strength)) -else -return string.format(" %d ° at %3.2f kps",Direction,UTILS.MpsToKnots(Strength)) -end -else -return" no wind" -end -return nil -end -function COORDINATE:Get3DDistance(TargetCoordinate) -local TargetVec3={x=TargetCoordinate.x,y=TargetCoordinate.y,z=TargetCoordinate.z} -local SourceVec3=self:GetVec3() -local dist=UTILS.VecDist3D(TargetVec3,SourceVec3) -return dist -end -function COORDINATE:GetBearingText(AngleRadians,Precision,Settings,MagVar) -local Settings=Settings or _SETTINGS -local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),Precision) -local s=string.format('%03d°',AngleDegrees) -if MagVar then -local variation=UTILS.GetMagneticDeclination()or 0 -local AngleMagnetic=AngleDegrees-variation -if AngleMagnetic<0 then AngleMagnetic=360-AngleMagnetic end -s=string.format('%03d°M|%03d°',AngleMagnetic,AngleDegrees) -end -return s -end -function COORDINATE:GetDistanceText(Distance,Settings,Language,Precision) -local Settings=Settings or _SETTINGS -local Language=Language or Settings.Locale or _SETTINGS.Locale or"EN" -Language=string.lower(Language) -local Precision=Precision or 0 -local DistanceText -if Settings:IsMetric()then -if Language=="en"then -DistanceText=" for "..UTILS.Round(Distance/1000,Precision).." km" -elseif Language=="ru"then -DistanceText=" за "..UTILS.Round(Distance/1000,Precision).." километров" -end -else -if Language=="en"then -DistanceText=" for "..UTILS.Round(UTILS.MetersToNM(Distance),Precision).." miles" -elseif Language=="ru"then -DistanceText=" за "..UTILS.Round(UTILS.MetersToNM(Distance),Precision).." миль" -end -end -return DistanceText -end -function COORDINATE:GetAltitudeText(Settings,Language) -local Altitude=self.y -local Settings=Settings or _SETTINGS -local Language=Language or Settings.Locale or _SETTINGS.Locale or"EN" -Language=string.lower(Language) -if Altitude~=0 then -if Settings:IsMetric()then -if Language=="en"then -return" at "..UTILS.Round(self.y,-3).." meters" -elseif Language=="ru"then -return" в "..UTILS.Round(self.y,-3).." метры" -end -else -if Language=="en"then -return" at "..UTILS.Round(UTILS.MetersToFeet(self.y),-3).." feet" -elseif Language=="ru"then -return" в "..UTILS.Round(self.y,-3).." ноги" -end -end -else -return"" -end -end -function COORDINATE:GetVelocityText(Settings) -local Velocity=self:GetVelocity() -local Settings=Settings or _SETTINGS -if Velocity then -if Settings:IsMetric()then -return string.format(" moving at %d km/h",UTILS.MpsToKmph(Velocity)) -else -return string.format(" moving at %d mi/h",UTILS.MpsToKmph(Velocity)/1.852) -end -else -return" stationary" -end -end -function COORDINATE:GetHeadingText(Settings) -local Heading=self:GetHeading() -if Heading then -return string.format(" bearing %3d°",Heading) -else -return" bearing unknown" -end -end -function COORDINATE:GetBRText(AngleRadians,Distance,Settings,Language,MagVar) -local Settings=Settings or _SETTINGS -local BearingText=self:GetBearingText(AngleRadians,0,Settings,MagVar) -local DistanceText=self:GetDistanceText(Distance,Settings,Language,0) -local BRText=BearingText..DistanceText -return BRText -end -function COORDINATE:GetBRAText(AngleRadians,Distance,Settings,Language,MagVar) -local Settings=Settings or _SETTINGS -local BearingText=self:GetBearingText(AngleRadians,0,Settings,MagVar) -local DistanceText=self:GetDistanceText(Distance,Settings,Language,0) -local AltitudeText=self:GetAltitudeText(Settings,Language) -local BRAText=BearingText..DistanceText..AltitudeText -return BRAText -end -function COORDINATE:SetAltitude(altitude,asl) -local alt=altitude -if asl then -alt=altitude -else -alt=self:GetLandHeight()+altitude -end -self.y=alt -return self -end -function COORDINATE:SetAtLandheight() -local alt=self:GetLandHeight() -self.y=alt -return self -end -function COORDINATE:WaypointAir(AltType,Type,Action,Speed,SpeedLocked,airbase,DCSTasks,description,timeReFuAr) -self:F2({AltType,Type,Action,Speed,SpeedLocked}) -AltType=AltType or"RADIO" -if SpeedLocked==nil then -SpeedLocked=true -end -Speed=Speed or 500 -local RoutePoint={} -RoutePoint.x=self.x -RoutePoint.y=self.z -RoutePoint.alt=self.y -RoutePoint.alt_type=AltType -RoutePoint.type=Type or nil -RoutePoint.action=Action or nil -RoutePoint.speed=Speed/3.6 -RoutePoint.speed_locked=SpeedLocked -RoutePoint.ETA=0 -RoutePoint.ETA_locked=false -RoutePoint.name=description -if airbase then -local AirbaseID=airbase:GetID() -local AirbaseCategory=airbase:GetAirbaseCategory() -if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then -RoutePoint.linkUnit=AirbaseID -RoutePoint.helipadId=AirbaseID -elseif AirbaseCategory==Airbase.Category.AIRDROME then -RoutePoint.airdromeId=AirbaseID -else -self:E("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!") -end -end -if Type==COORDINATE.WaypointType.LandingReFuAr then -RoutePoint.timeReFuAr=timeReFuAr or 10 -end -RoutePoint.task={} -RoutePoint.task.id="ComboTask" -RoutePoint.task.params={} -RoutePoint.task.params.tasks=DCSTasks or{} -self:T({RoutePoint=RoutePoint}) -return RoutePoint -end -function COORDINATE:WaypointAirTurningPoint(AltType,Speed,DCSTasks,description) -return self:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,description) -end -function COORDINATE:WaypointAirFlyOverPoint(AltType,Speed) -return self:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.FlyoverPoint,Speed) -end -function COORDINATE:WaypointAirTakeOffParkingHot(AltType,Speed) -return self:WaypointAir(AltType,COORDINATE.WaypointType.TakeOffParkingHot,COORDINATE.WaypointAction.FromParkingAreaHot,Speed) -end -function COORDINATE:WaypointAirTakeOffParking(AltType,Speed) -return self:WaypointAir(AltType,COORDINATE.WaypointType.TakeOffParking,COORDINATE.WaypointAction.FromParkingArea,Speed) -end -function COORDINATE:WaypointAirTakeOffRunway(AltType,Speed) -return self:WaypointAir(AltType,COORDINATE.WaypointType.TakeOff,COORDINATE.WaypointAction.FromRunway,Speed) -end -function COORDINATE:WaypointAirLanding(Speed,airbase,DCSTasks,description) -return self:WaypointAir(nil,COORDINATE.WaypointType.Land,COORDINATE.WaypointAction.Landing,Speed,false,airbase,DCSTasks,description) -end -function COORDINATE:WaypointAirLandingReFu(Speed,airbase,timeReFuAr,DCSTasks,description) -return self:WaypointAir(nil,COORDINATE.WaypointType.LandingReFuAr,COORDINATE.WaypointAction.LandingReFuAr,Speed,false,airbase,DCSTasks,description,timeReFuAr or 10) -end -function COORDINATE:WaypointGround(Speed,Formation,DCSTasks) -self:F2({Speed,Formation,DCSTasks}) -local RoutePoint={} -RoutePoint.x=self.x -RoutePoint.y=self.z -RoutePoint.alt=self:GetLandHeight()+1 -RoutePoint.alt_type=COORDINATE.WaypointAltType.BARO -RoutePoint.type="Turning Point" -RoutePoint.action=Formation or"Off Road" -RoutePoint.formation_template="" -RoutePoint.ETA=0 -RoutePoint.ETA_locked=false -RoutePoint.speed=(Speed or 20)/3.6 -RoutePoint.speed_locked=true -RoutePoint.task={} -RoutePoint.task.id="ComboTask" -RoutePoint.task.params={} -RoutePoint.task.params.tasks=DCSTasks or{} -return RoutePoint -end -function COORDINATE:WaypointNaval(Speed,Depth,DCSTasks) -self:F2({Speed,Depth,DCSTasks}) -local RoutePoint={} -RoutePoint.x=self.x -RoutePoint.y=self.z -RoutePoint.alt=Depth or self.y -RoutePoint.alt_type="BARO" -RoutePoint.type="Turning Point" -RoutePoint.action="Turning Point" -RoutePoint.formation_template="" -RoutePoint.ETA=0 -RoutePoint.ETA_locked=false -RoutePoint.speed=(Speed or 20)/3.6 -RoutePoint.speed_locked=true -RoutePoint.task={} -RoutePoint.task.id="ComboTask" -RoutePoint.task.params={} -RoutePoint.task.params.tasks=DCSTasks or{} -return RoutePoint -end -function COORDINATE:GetClosestAirbase(Category,Coalition) -local airbases=AIRBASE.GetAllAirbases(Coalition) -local closest=nil -local distmin=nil -for _,_airbase in pairs(airbases)do -local airbase=_airbase -if airbase then -local category=airbase:GetAirbaseCategory() -if Category and Category==category or Category==nil then -local dist=self:Get2DDistance(airbase:GetCoordinate()) -if closest==nil then -distmin=dist -closest=airbase -else -if dist=2 then -for i=1,#Path-1 do -Way=Way+Path[i+1]:Get2DDistance(Path[i]) -end -else -return nil,nil,false -end -return Path,Way,GotPath -end -function COORDINATE:GetSurfaceType() -local vec2=self:GetVec2() -local surface=land.getSurfaceType(vec2) -return surface -end -function COORDINATE:IsSurfaceTypeLand() -return self:GetSurfaceType()==land.SurfaceType.LAND -end -function COORDINATE:IsSurfaceTypeLand() -return self:GetSurfaceType()==land.SurfaceType.LAND -end -function COORDINATE:IsSurfaceTypeRoad() -return self:GetSurfaceType()==land.SurfaceType.ROAD -end -function COORDINATE:IsSurfaceTypeRunway() -return self:GetSurfaceType()==land.SurfaceType.RUNWAY -end -function COORDINATE:IsSurfaceTypeShallowWater() -return self:GetSurfaceType()==land.SurfaceType.SHALLOW_WATER -end -function COORDINATE:IsSurfaceTypeWater() -return self:GetSurfaceType()==land.SurfaceType.WATER -end -function COORDINATE:Explosion(ExplosionIntensity,Delay) -ExplosionIntensity=ExplosionIntensity or 100 -if Delay and Delay>0 then -self:ScheduleOnce(Delay,self.Explosion,self,ExplosionIntensity) -else -trigger.action.explosion(self:GetVec3(),ExplosionIntensity) -end -return self -end -function COORDINATE:IlluminationBomb(Power,Delay) -Power=Power or 1000 -if Delay and Delay>0 then -self:ScheduleOnce(Delay,self.IlluminationBomb,self,Power) -else -trigger.action.illuminationBomb(self:GetVec3(),Power) -end -return self -end -function COORDINATE:Smoke(SmokeColor) -self:F2({SmokeColor}) -trigger.action.smoke(self:GetVec3(),SmokeColor) -end -function COORDINATE:SmokeGreen() -self:F2() -self:Smoke(SMOKECOLOR.Green) -end -function COORDINATE:SmokeRed() -self:F2() -self:Smoke(SMOKECOLOR.Red) -end -function COORDINATE:SmokeWhite() -self:F2() -self:Smoke(SMOKECOLOR.White) -end -function COORDINATE:SmokeOrange() -self:F2() -self:Smoke(SMOKECOLOR.Orange) -end -function COORDINATE:SmokeBlue() -self:F2() -self:Smoke(SMOKECOLOR.Blue) -end -function COORDINATE:BigSmokeAndFire(preset,density,name) -self:F2({preset=preset,density=density}) -density=density or 0.5 -self.firename=name or"Fire-"..math.random(1,10000) -trigger.action.effectSmokeBig(self:GetVec3(),preset,density,self.firename) -end -function COORDINATE:StopBigSmokeAndFire(name) -name=name or self.firename -trigger.action.effectSmokeStop(name) -end -function COORDINATE:BigSmokeAndFireSmall(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire,density,name) -end -function COORDINATE:BigSmokeAndFireMedium(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire,density,name) -end -function COORDINATE:BigSmokeAndFireLarge(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire,density,name) -end -function COORDINATE:BigSmokeAndFireHuge(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire,density,name) -end -function COORDINATE:BigSmokeSmall(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke,density,name) -end -function COORDINATE:BigSmokeMedium(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke,density,name) -end -function COORDINATE:BigSmokeLarge(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke,density,name) -end -function COORDINATE:BigSmokeHuge(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke,density,name) -end -function COORDINATE:Flare(FlareColor,Azimuth) -self:F2({FlareColor}) -trigger.action.signalFlare(self:GetVec3(),FlareColor,Azimuth and Azimuth or 0) -end -function COORDINATE:FlareWhite(Azimuth) -self:F2(Azimuth) -self:Flare(FLARECOLOR.White,Azimuth) -end -function COORDINATE:FlareYellow(Azimuth) -self:F2(Azimuth) -self:Flare(FLARECOLOR.Yellow,Azimuth) -end -function COORDINATE:FlareGreen(Azimuth) -self:F2(Azimuth) -self:Flare(FLARECOLOR.Green,Azimuth) -end -function COORDINATE:FlareRed(Azimuth) -self:F2(Azimuth) -self:Flare(FLARECOLOR.Red,Azimuth) -end -do -function COORDINATE:MarkToAll(MarkText,ReadOnly,Text) -local MarkID=UTILS.GetMarkID() -if ReadOnly==nil then -ReadOnly=false -end -local text=Text or"" -trigger.action.markToAll(MarkID,MarkText,self:GetVec3(),ReadOnly,text) -return MarkID -end -function COORDINATE:MarkToCoalition(MarkText,Coalition,ReadOnly,Text) -local MarkID=UTILS.GetMarkID() -if ReadOnly==nil then -ReadOnly=false -end -local text=Text or"" -trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),Coalition,ReadOnly,text) -return MarkID -end -function COORDINATE:MarkToCoalitionRed(MarkText,ReadOnly,Text) -return self:MarkToCoalition(MarkText,coalition.side.RED,ReadOnly,Text) -end -function COORDINATE:MarkToCoalitionBlue(MarkText,ReadOnly,Text) -return self:MarkToCoalition(MarkText,coalition.side.BLUE,ReadOnly,Text) -end -function COORDINATE:MarkToGroup(MarkText,MarkGroup,ReadOnly,Text) -local MarkID=UTILS.GetMarkID() -if ReadOnly==nil then -ReadOnly=false -end -local text=Text or"" -trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),MarkGroup:GetID(),ReadOnly,text) -return MarkID -end -function COORDINATE:RemoveMark(MarkID) -trigger.action.removeMark(MarkID) -end -function COORDINATE:LineToAll(Endpoint,Coalition,Color,Alpha,LineType,ReadOnly,Text) -local MarkID=UTILS.GetMarkID() -if ReadOnly==nil then -ReadOnly=false -end -local vec3=Endpoint:GetVec3() -Coalition=Coalition or-1 -Color=Color or{1,0,0} -Color[4]=Alpha or 1.0 -LineType=LineType or 1 -trigger.action.lineToAll(Coalition,MarkID,self:GetVec3(),vec3,Color,LineType,ReadOnly,Text or"") -return MarkID -end -function COORDINATE:CircleToAll(Radius,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text) -local MarkID=UTILS.GetMarkID() -if ReadOnly==nil then -ReadOnly=false -end -local vec3=self:GetVec3() -Radius=Radius or 1000 -Coalition=Coalition or-1 -Color=Color or{1,0,0} -Color[4]=Alpha or 1.0 -LineType=LineType or 1 -FillColor=FillColor or UTILS.DeepCopy(Color) -FillColor[4]=FillAlpha or 0.15 -trigger.action.circleToAll(Coalition,MarkID,vec3,Radius,Color,FillColor,LineType,ReadOnly,Text or"") -return MarkID -end -end -function COORDINATE:RectToAll(Endpoint,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text) -local MarkID=UTILS.GetMarkID() -if ReadOnly==nil then -ReadOnly=false -end -local vec3=Endpoint:GetVec3() -Coalition=Coalition or-1 -Color=Color or{1,0,0} -Color[4]=Alpha or 1.0 -LineType=LineType or 1 -FillColor=FillColor or UTILS.DeepCopy(Color) -FillColor[4]=FillAlpha or 0.15 -trigger.action.rectToAll(Coalition,MarkID,self:GetVec3(),vec3,Color,FillColor,LineType,ReadOnly,Text or"") -return MarkID -end -function COORDINATE:QuadToAll(Coord2,Coord3,Coord4,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text) -local MarkID=UTILS.GetMarkID() -if ReadOnly==nil then -ReadOnly=false -end -local point1=self:GetVec3() -local point2=Coord2:GetVec3() -local point3=Coord3:GetVec3() -local point4=Coord4:GetVec3() -Coalition=Coalition or-1 -Color=Color or{1,0,0} -Color[4]=Alpha or 1.0 -LineType=LineType or 1 -FillColor=FillColor or UTILS.DeepCopy(Color) -FillColor[4]=FillAlpha or 0.15 -trigger.action.quadToAll(Coalition,MarkID,point1,point2,point3,point4,Color,FillColor,LineType,ReadOnly,Text or"") -return MarkID -end -function COORDINATE:MarkupToAllFreeForm(Coordinates,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text) -local MarkID=UTILS.GetMarkID() -if ReadOnly==nil then -ReadOnly=false -end -Coalition=Coalition or-1 -Color=Color or{1,0,0} -Color[4]=Alpha or 1.0 -LineType=LineType or 1 -FillColor=FillColor or UTILS.DeepCopy(Color) -FillColor[4]=FillAlpha or 0.15 -local vecs={} -vecs[1]=self:GetVec3() -for i,coord in ipairs(Coordinates)do -vecs[i+1]=coord:GetVec3() -end -if#vecs<3 then -self:E("ERROR: A free form polygon needs at least three points!") -elseif#vecs==3 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],Color,FillColor,LineType,ReadOnly,Text or"") -elseif#vecs==4 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],Color,FillColor,LineType,ReadOnly,Text or"") -elseif#vecs==5 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],Color,FillColor,LineType,ReadOnly,Text or"") -elseif#vecs==6 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],Color,FillColor,LineType,ReadOnly,Text or"") -elseif#vecs==7 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],Color,FillColor,LineType,ReadOnly,Text or"") -elseif#vecs==8 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],Color,FillColor,LineType,ReadOnly,Text or"") -elseif#vecs==9 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],Color,FillColor,LineType,ReadOnly,Text or"") -elseif#vecs==10 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],Color,FillColor,LineType,ReadOnly,Text or"") -elseif#vecs==11 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10], -vecs[11], -Color,FillColor,LineType,ReadOnly,Text or"") -elseif#vecs==12 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10], -vecs[11],vecs[12], -Color,FillColor,LineType,ReadOnly,Text or"") -elseif#vecs==13 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10], -vecs[11],vecs[12],vecs[13], -Color,FillColor,LineType,ReadOnly,Text or"") -elseif#vecs==14 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10], -vecs[11],vecs[12],vecs[13],vecs[14], -Color,FillColor,LineType,ReadOnly,Text or"") -elseif#vecs==15 then -trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10], -vecs[11],vecs[12],vecs[13],vecs[14],vecs[15], -Color,FillColor,LineType,ReadOnly,Text or"") -else -local s=string.format("trigger.action.markupToAll(7, %d, %d,",Coalition,MarkID) -for _,vec in pairs(vecs)do -s=s..string.format("{x=%.1f, y=%.1f, z=%.1f},",vec.x,vec.y,vec.z) -end -s=s..string.format("{%.3f, %.3f, %.3f, %.3f},",Color[1],Color[2],Color[3],Color[4]) -s=s..string.format("{%.3f, %.3f, %.3f, %.3f},",FillColor[1],FillColor[2],FillColor[3],FillColor[4]) -s=s..string.format("%d,",LineType or 1) -s=s..string.format("%s",tostring(ReadOnly)) -if Text and type(Text)=="string"and string.len(Text)>0 then -s=s..string.format(", \"%s\"",tostring(Text)) -end -s=s..")" -local success=UTILS.DoString(s) -if not success then -self:E("ERROR: Could not draw polygon") -env.info(s) -end -end -return MarkID -end -function COORDINATE:TextToAll(Text,Coalition,Color,Alpha,FillColor,FillAlpha,FontSize,ReadOnly) -local MarkID=UTILS.GetMarkID() -if ReadOnly==nil then -ReadOnly=false -end -Coalition=Coalition or-1 -Color=Color or{1,0,0} -Color[4]=Alpha or 1.0 -FillColor=FillColor or UTILS.DeepCopy(Color) -FillColor[4]=FillAlpha or 0.3 -FontSize=FontSize or 14 -trigger.action.textToAll(Coalition,MarkID,self:GetVec3(),Color,FillColor,FontSize,ReadOnly,Text or"Hello World") -return MarkID -end -function COORDINATE:ArrowToAll(Endpoint,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text) -local MarkID=UTILS.GetMarkID() -if ReadOnly==nil then -ReadOnly=false -end -local vec3=Endpoint:GetVec3() -Coalition=Coalition or-1 -Color=Color or{1,0,0} -Color[4]=Alpha or 1.0 -LineType=LineType or 1 -FillColor=FillColor or UTILS.DeepCopy(Color) -FillColor[4]=FillAlpha or 0.15 -trigger.action.arrowToAll(Coalition,MarkID,vec3,self:GetVec3(),Color,FillColor,LineType,ReadOnly,Text or"") -return MarkID -end -function COORDINATE:IsLOS(ToCoordinate,Offset) -Offset=Offset or 2 -local FromVec3=self:GetVec3() -FromVec3.y=FromVec3.y+Offset -local ToVec3=ToCoordinate:GetVec3() -ToVec3.y=ToVec3.y+Offset -local IsLOS=land.isVisible(FromVec3,ToVec3) -return IsLOS -end -function COORDINATE:IsInRadius(Coordinate,Radius) -local InVec2=self:GetVec2() -local Vec2=Coordinate:GetVec2() -local InRadius=UTILS.IsInRadius(InVec2,Vec2,Radius) -return InRadius -end -function COORDINATE:IsInSphere(Coordinate,Radius) -local InVec3=self:GetVec3() -local Vec3=Coordinate:GetVec3() -local InSphere=UTILS.IsInSphere(InVec3,Vec3,Radius) -return InSphere -end -function COORDINATE:GetSunriseAtDate(Day,Month,Year,InSeconds) -local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day) -local Latitude,Longitude=self:GetLLDDM() -local Tdiff=UTILS.GMTToLocalTimeDifference() -local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) -if InSeconds then -return sunrise -else -return UTILS.SecondsToClock(sunrise,true) -end -end -function COORDINATE:GetSunriseAtDayOfYear(DayOfYear,InSeconds) -local Latitude,Longitude=self:GetLLDDM() -local Tdiff=UTILS.GMTToLocalTimeDifference() -local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) -if InSeconds then -return sunrise -else -return UTILS.SecondsToClock(sunrise,true) -end -end -function COORDINATE:GetSunrise(InSeconds) -local DayOfYear=UTILS.GetMissionDayOfYear() -local Latitude,Longitude=self:GetLLDDM() -local Tdiff=UTILS.GMTToLocalTimeDifference() -local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) -local date=UTILS.GetDCSMissionDate() -if InSeconds then -return sunrise -else -return UTILS.SecondsToClock(sunrise,true) -end -end -function COORDINATE:GetMinutesToSunrise(OnlyToday) -local time=UTILS.SecondsOfToday() -local sunrise=nil -local delta=nil -if OnlyToday then -sunrise=self:GetSunrise(true) -delta=sunrise-time -else -local DayOfYear=UTILS.GetMissionDayOfYear()+1 -local Latitude,Longitude=self:GetLLDDM() -local Tdiff=UTILS.GMTToLocalTimeDifference() -sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) -delta=sunrise+UTILS.SecondsToMidnight() -end -return delta/60 -end -function COORDINATE:IsDay(Clock) -if Clock then -local Time=UTILS.ClockToSeconds(Clock) -local clock=UTILS.Split(Clock,"+")[1] -local DayOfYear=UTILS.GetMissionDayOfYear(Time) -local Latitude,Longitude=self:GetLLDDM() -local Tdiff=UTILS.GMTToLocalTimeDifference() -local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) -local sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff) -local time=UTILS.ClockToSeconds(clock) -if time>sunrise and time<=sunset then -return true -else -return false -end -else -local sunrise=self:GetSunrise(true) -local sunset=self:GetSunset(true) -local time=UTILS.SecondsOfToday() -if time>sunrise and time<=sunset then -return true -else -return false -end -end -end -function COORDINATE:IsNight(Clock) -return not self:IsDay(Clock) -end -function COORDINATE:GetSunsetAtDate(Day,Month,Year,InSeconds) -local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day) -local Latitude,Longitude=self:GetLLDDM() -local Tdiff=UTILS.GMTToLocalTimeDifference() -local sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff) -if InSeconds then -return sunset -else -return UTILS.SecondsToClock(sunset,true) -end -end -function COORDINATE:GetSunset(InSeconds) -local DayOfYear=UTILS.GetMissionDayOfYear() -local Latitude,Longitude=self:GetLLDDM() -local Tdiff=UTILS.GMTToLocalTimeDifference() -local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff) -local date=UTILS.GetDCSMissionDate() -if InSeconds then -return sunrise -else -return UTILS.SecondsToClock(sunrise,true) -end -end -function COORDINATE:GetMinutesToSunset(OnlyToday) -local time=UTILS.SecondsOfToday() -local sunset=nil -local delta=nil -if OnlyToday then -sunset=self:GetSunset(true) -delta=sunset-time -else -local DayOfYear=UTILS.GetMissionDayOfYear()+1 -local Latitude,Longitude=self:GetLLDDM() -local Tdiff=UTILS.GMTToLocalTimeDifference() -sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff) -delta=sunset+UTILS.SecondsToMidnight() -end -return delta/60 -end -function COORDINATE:ToStringBR(FromCoordinate,Settings,MagVar) -local DirectionVec3=FromCoordinate:GetDirectionVec3(self) -local AngleRadians=self:GetAngleRadians(DirectionVec3) -local Distance=self:Get2DDistance(FromCoordinate) -return"BR, "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar) -end -function COORDINATE:ToStringBRA(FromCoordinate,Settings,MagVar) -local DirectionVec3=FromCoordinate:GetDirectionVec3(self) -local AngleRadians=self:GetAngleRadians(DirectionVec3) -local Distance=FromCoordinate:Get2DDistance(self) -local Altitude=self:GetAltitudeText() -return"BRA, "..self:GetBRAText(AngleRadians,Distance,Settings,nil,MagVar) -end -function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades,SSML,Angels,Zeros) -local BRAANATO="Merged." -local currentCoord=FromCoordinate -local DirectionVec3=FromCoordinate:GetDirectionVec3(self) -local AngleRadians=self:GetAngleRadians(DirectionVec3) -local bearing=UTILS.Round(UTILS.ToDegree(AngleRadians),0) -local rangeMetres=self:Get2DDistance(currentCoord) -local rangeNM=UTILS.Round(UTILS.MetersToNM(rangeMetres),0) -local aspect=self:ToStringAspect(currentCoord) -local alt=UTILS.Round(UTILS.MetersToFeet(self.y)/1000,0) -local alttext=string.format("%d thousand",alt) -if Angels then -alttext=string.format("Angels %d",alt) -end -if alt<1 then -alttext="very low" -end -local track="Maneuver" -if self.Heading then -track=UTILS.BearingToCardinal(self.Heading)or"North" -end -if rangeNM>3 then -if SSML then -if Zeros then -bearing=string.format("%03d",bearing) -local AngleDegText=string.gsub(bearing,"%d","%1 ") -AngleDegText=string.gsub(AngleDegText," $","") -AngleDegText=string.gsub(AngleDegText,"0","zero") -if aspect==""then -BRAANATO=string.format("brah %s, %d miles, %s, Track %s",AngleDegText,rangeNM,alttext,track) -else -BRAANATO=string.format("brah %s, %d miles, %s, %s, Track %s",AngleDegText,rangeNM,alttext,aspect,track) -end -else -if aspect==""then -BRAANATO=string.format("brah %03d, %d miles, %s, Track %s",bearing,rangeNM,alttext,track) -else -BRAANATO=string.format("brah %03d, %d miles, %s, %s, Track %s",bearing,rangeNM,alttext,aspect,track) -end -end -if Bogey and Spades then -BRAANATO=BRAANATO..", Bogey, Spades." -elseif Bogey then -BRAANATO=BRAANATO..", Bogey." -elseif Spades then -BRAANATO=BRAANATO..", Spades." -else -BRAANATO=BRAANATO.."." -end -else -if aspect==""then -BRAANATO=string.format("BRA %03d, %d miles, %s, Track %s",bearing,rangeNM,alttext,track) -else -BRAANATO=string.format("BRAA %03d, %d miles, %s, %s, Track %s",bearing,rangeNM,alttext,aspect,track) -end -if Bogey and Spades then -BRAANATO=BRAANATO..", Bogey, Spades." -elseif Bogey then -BRAANATO=BRAANATO..", Bogey." -elseif Spades then -BRAANATO=BRAANATO..", Spades." -else -BRAANATO=BRAANATO.."." -end -end -end -return BRAANATO -end -function COORDINATE.GetBullseyeCoordinate(Coalition) -return COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition)) -end -function COORDINATE:ToStringBULLS(Coalition,Settings,MagVar) -local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition)) -local DirectionVec3=BullsCoordinate:GetDirectionVec3(self) -local AngleRadians=self:GetAngleRadians(DirectionVec3) -local Distance=self:Get2DDistance(BullsCoordinate) -local Altitude=self:GetAltitudeText() -return"BULLS, "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar) -end -function COORDINATE:ToStringAspect(TargetCoordinate) -local Heading=self.Heading -local DirectionVec3=self:GetDirectionVec3(TargetCoordinate) -local Angle=self:GetAngleDegrees(DirectionVec3) -if Heading then -local Aspect=Angle-Heading -if Aspect>-135 and Aspect<=-45 then -return"Flanking" -end -if Aspect>-45 and Aspect<=45 then -return"Hot" -end -if Aspect>45 and Aspect<=135 then -return"Flanking" -end -if Aspect>135 or Aspect<=-135 then -return"Cold" -end -end -return"" -end -function COORDINATE:GetLLDDM() -return coord.LOtoLL(self:GetVec3()) -end -function COORDINATE:ToStringLL(Settings) -local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy -local lat,lon=coord.LOtoLL(self:GetVec3()) -return string.format('%f',lat)..' '..string.format('%f',lon) -end -function COORDINATE:ToStringLLDMS(Settings) -local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy -local lat,lon=coord.LOtoLL(self:GetVec3()) -return"LL DMS "..UTILS.tostringLL(lat,lon,LL_Accuracy,true) -end -function COORDINATE:ToStringLLDDM(Settings) -local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy -local lat,lon=coord.LOtoLL(self:GetVec3()) -return"LL DDM "..UTILS.tostringLL(lat,lon,LL_Accuracy,false) -end -function COORDINATE:ToStringMGRS(Settings) -local MGRS_Accuracy=Settings and Settings.MGRS_Accuracy or _SETTINGS.MGRS_Accuracy -local lat,lon=coord.LOtoLL(self:GetVec3()) -local MGRS=coord.LLtoMGRS(lat,lon) -return"MGRS "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy) -end -function COORDINATE:NewFromMGRSString(MGRSString) -local myparts=UTILS.Split(MGRSString," ") -local northing=tostring(myparts[5])or"" -local easting=tostring(myparts[4])or"" -if string.len(easting)<5 then easting=easting..string.rep("0",5-string.len(easting))end -if string.len(northing)<5 then northing=northing..string.rep("0",5-string.len(northing))end -local MGRS={ -UTMZone=myparts[2], -MGRSDigraph=myparts[3], -Easting=easting, -Northing=northing, -} -local lat,lon=coord.MGRStoLL(MGRS) -local point=coord.LLtoLO(lat,lon,0) -local coord=COORDINATE:NewFromVec2({x=point.x,y=point.z}) -return coord -end -function COORDINATE:NewFromMGRS(UTMZone,MGRSDigraph,Easting,Northing) -if string.len(Easting)<5 then Easting=Easting..string.rep("0",5-string.len(Easting))end -if string.len(Northing)<5 then Northing=Northing..string.rep("0",5-string.len(Northing))end -local MGRS={ -UTMZone=UTMZone, -MGRSDigraph=MGRSDigraph, -Easting=Easting, -Northing=Northing, -} -local lat,lon=coord.MGRStoLL(MGRS) -local point=coord.LLtoLO(lat,lon,0) -local coord=COORDINATE:NewFromVec2({x=point.x,y=point.z}) -end -function COORDINATE:ToStringFromRP(ReferenceCoord,ReferenceName,Controllable,Settings,MagVar) -self:F2({ReferenceCoord=ReferenceCoord,ReferenceName=ReferenceName}) -local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS -local IsAir=Controllable and Controllable:IsAirPlane()or false -if IsAir then -local DirectionVec3=ReferenceCoord:GetDirectionVec3(self) -local AngleRadians=self:GetAngleRadians(DirectionVec3) -local Distance=self:Get2DDistance(ReferenceCoord) -return"Targets are the last seen "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName -else -local DirectionVec3=ReferenceCoord:GetDirectionVec3(self) -local AngleRadians=self:GetAngleRadians(DirectionVec3) -local Distance=self:Get2DDistance(ReferenceCoord) -return"Target are located "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName -end -return nil -end -function COORDINATE:ToStringFromRPShort(ReferenceCoord,ReferenceName,Controllable,Settings,MagVar) -self:F2({ReferenceCoord=ReferenceCoord,ReferenceName=ReferenceName}) -local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS -local IsAir=Controllable and Controllable:IsAirPlane()or false -if IsAir then -local DirectionVec3=ReferenceCoord:GetDirectionVec3(self) -local AngleRadians=self:GetAngleRadians(DirectionVec3) -local Distance=self:Get2DDistance(ReferenceCoord) -return self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName -else -local DirectionVec3=ReferenceCoord:GetDirectionVec3(self) -local AngleRadians=self:GetAngleRadians(DirectionVec3) -local Distance=self:Get2DDistance(ReferenceCoord) -return self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName -end -return nil -end -function COORDINATE:ToStringA2G(Controllable,Settings,MagVar) -self:F2({Controllable=Controllable and Controllable:GetName()}) -local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS -if Settings:IsA2G_BR()then -if Controllable then -local Coordinate=Controllable:GetCoordinate() -return Controllable and self:ToStringBR(Coordinate,Settings,MagVar)or self:ToStringMGRS(Settings) -else -return self:ToStringMGRS(Settings) -end -end -if Settings:IsA2G_LL_DMS()then -return self:ToStringLLDMS(Settings) -end -if Settings:IsA2G_LL_DDM()then -return self:ToStringLLDDM(Settings) -end -if Settings:IsA2G_MGRS()then -return self:ToStringMGRS(Settings) -end -return nil -end -function COORDINATE:ToStringA2A(Controllable,Settings,MagVar) -self:F2({Controllable=Controllable and Controllable:GetName()}) -local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS -if Settings:IsA2A_BRAA()then -if Controllable then -local Coordinate=Controllable:GetCoordinate() -return self:ToStringBRA(Coordinate,Settings,MagVar) -else -return self:ToStringMGRS(Settings) -end -end -if Settings:IsA2A_BULLS()then -local Coalition=Controllable:GetCoalition() -return self:ToStringBULLS(Coalition,Settings,MagVar) -end -if Settings:IsA2A_LL_DMS()then -return self:ToStringLLDMS(Settings) -end -if Settings:IsA2A_LL_DDM()then -return self:ToStringLLDDM(Settings) -end -if Settings:IsA2A_MGRS()then -return self:ToStringMGRS(Settings) -end -return nil -end -function COORDINATE:ToString(Controllable,Settings,Task) -local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS -local ModeA2A=nil -if Task then -if Task:IsInstanceOf(TASK_A2A)then -ModeA2A=true -else -if Task:IsInstanceOf(TASK_A2G)then -ModeA2A=false -else -if Task:IsInstanceOf(TASK_CARGO)then -ModeA2A=false -end -if Task:IsInstanceOf(TASK_CAPTURE_ZONE)then -ModeA2A=false -end -end -end -end -if ModeA2A==nil then -local IsAir=Controllable and(Controllable:IsAirPlane()or Controllable:IsHelicopter())or false -if IsAir then -ModeA2A=true -else -ModeA2A=false -end -end -if ModeA2A==true then -return self:ToStringA2A(Controllable,Settings) -else -return self:ToStringA2G(Controllable,Settings) -end -return nil -end -function COORDINATE:ToStringPressure(Controllable,Settings) -self:F2({Controllable=Controllable and Controllable:GetName()}) -local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS -return self:GetPressureText(nil,Settings) -end -function COORDINATE:ToStringWind(Controllable,Settings) -self:F2({Controllable=Controllable and Controllable:GetName()}) -local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS -return self:GetWindText(nil,Settings) -end -function COORDINATE:ToStringTemperature(Controllable,Settings) -self:F2({Controllable=Controllable and Controllable:GetName()}) -local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS -return self:GetTemperatureText(nil,Settings) -end -function COORDINATE:IsInSteepArea(Radius,Minelevation) -local steep=false -local elev=Minelevation or 8 -local bdelta=0 -local h0=self:GetLandHeight() -local radius=Radius or 50 -local diam=radius*2 -for i=0,150,30 do -local polar=math.fmod(i+180,360) -local c1=self:Translate(radius,i,false,false) -local c2=self:Translate(radius,polar,false,false) -local h1=c1:GetLandHeight() -local h2=c2:GetLandHeight() -local d1=math.abs(h1-h2) -local d2=math.abs(h0-h1) -local d3=math.abs(h0-h2) -local dm=d1>d2 and d1 or d2 -local dm1=dm>d3 and dm or d3 -bdelta=dm1>bdelta and dm1 or bdelta -self:T(string.format("d1=%d, d2=%d, d3=%d, max delta=%d",d1,d2,d3,bdelta)) -end -local steepness=bdelta/(radius/100) -if steepness>=elev then steep=true end -return steep,math.floor(steepness) -end -function COORDINATE:IsInFlatArea(Radius,Minelevation) -local steep,elev=self:IsInSteepArea(Radius,Minelevation) -local flat=not steep -return flat,elev -end -end -do -POINT_VEC3={ -ClassName="POINT_VEC3", -Metric=true, -RoutePointAltType={ -BARO="BARO", -}, -RoutePointType={ -TakeOffParking="TakeOffParking", -TurningPoint="Turning Point", -}, -RoutePointAction={ -FromParkingArea="From Parking Area", -TurningPoint="Turning Point", -}, -} -function POINT_VEC3:New(x,y,z) -local self=BASE:Inherit(self,COORDINATE:New(x,y,z)) -self:F2(self) -return self -end -function POINT_VEC3:NewFromVec2(Vec2,LandHeightAdd) -local self=BASE:Inherit(self,COORDINATE:NewFromVec2(Vec2,LandHeightAdd)) -self:F2(self) -return self -end -function POINT_VEC3:NewFromVec3(Vec3) -local self=BASE:Inherit(self,COORDINATE:NewFromVec3(Vec3)) -self:F2(self) -return self -end -function POINT_VEC3:GetX() -return self.x -end -function POINT_VEC3:GetY() -return self.y -end -function POINT_VEC3:GetZ() -return self.z -end -function POINT_VEC3:SetX(x) -self.x=x -return self -end -function POINT_VEC3:SetY(y) -self.y=y -return self -end -function POINT_VEC3:SetZ(z) -self.z=z -return self -end -function POINT_VEC3:AddX(x) -self.x=self.x+x -return self -end -function POINT_VEC3:AddY(y) -self.y=self.y+y -return self -end -function POINT_VEC3:AddZ(z) -self.z=self.z+z -return self -end -function POINT_VEC3:GetRandomPointVec3InRadius(OuterRadius,InnerRadius) -return POINT_VEC3:NewFromVec3(self:GetRandomVec3InRadius(OuterRadius,InnerRadius)) -end -end -do -POINT_VEC2={ -ClassName="POINT_VEC2", -} -function POINT_VEC2:New(x,y,LandHeightAdd) -local LandHeight=land.getHeight({["x"]=x,["y"]=y}) -LandHeightAdd=LandHeightAdd or 0 -LandHeight=LandHeight+LandHeightAdd -local self=BASE:Inherit(self,COORDINATE:New(x,LandHeight,y)) -self:F2(self) -return self -end -function POINT_VEC2:NewFromVec2(Vec2,LandHeightAdd) -local LandHeight=land.getHeight(Vec2) -LandHeightAdd=LandHeightAdd or 0 -LandHeight=LandHeight+LandHeightAdd -local self=BASE:Inherit(self,COORDINATE:NewFromVec2(Vec2,LandHeightAdd)) -self:F2(self) -return self -end -function POINT_VEC2:NewFromVec3(Vec3) -local self=BASE:Inherit(self,COORDINATE:NewFromVec3(Vec3)) -self:F2(self) -return self -end -function POINT_VEC2:GetX() -return self.x -end -function POINT_VEC2:GetY() -return self.z -end -function POINT_VEC2:SetX(x) -self.x=x -return self -end -function POINT_VEC2:SetY(y) -self.z=y -return self -end -function POINT_VEC2:GetLat() -return self.x -end -function POINT_VEC2:SetLat(x) -self.x=x -return self -end -function POINT_VEC2:GetLon() -return self.z -end -function POINT_VEC2:SetLon(z) -self.z=z -return self -end -function POINT_VEC2:GetAlt() -return self.y~=0 or land.getHeight({x=self.x,y=self.z}) -end -function POINT_VEC2:SetAlt(Altitude) -self.y=Altitude or land.getHeight({x=self.x,y=self.z}) -return self -end -function POINT_VEC2:AddX(x) -self.x=self.x+x -return self -end -function POINT_VEC2:AddY(y) -self.z=self.z+y -return self -end -function POINT_VEC2:AddAlt(Altitude) -self.y=land.getHeight({x=self.x,y=self.z})+Altitude or 0 -return self -end -function POINT_VEC2:GetRandomPointVec2InRadius(OuterRadius,InnerRadius) -self:F2({OuterRadius,InnerRadius}) -return POINT_VEC2:NewFromVec2(self:GetRandomVec2InRadius(OuterRadius,InnerRadius)) -end -function POINT_VEC2:DistanceFromPointVec2(PointVec2Reference) -self:F2(PointVec2Reference) -local Distance=((PointVec2Reference.x-self.x)^2+(PointVec2Reference.z-self.z)^2)^0.5 -self:T2(Distance) -return Distance -end -end -do -VELOCITY={ -ClassName="VELOCITY", -} -function VELOCITY:New(VelocityMps) -local self=BASE:Inherit(self,BASE:New()) -self:F({}) -self.Velocity=VelocityMps -return self -end -function VELOCITY:Set(VelocityMps) -self.Velocity=VelocityMps -return self -end -function VELOCITY:Get() -return self.Velocity -end -function VELOCITY:SetKmph(VelocityKmph) -self.Velocity=UTILS.KmphToMps(VelocityKmph) -return self -end -function VELOCITY:GetKmph() -return UTILS.MpsToKmph(self.Velocity) -end -function VELOCITY:SetMiph(VelocityMiph) -self.Velocity=UTILS.MiphToMps(VelocityMiph) -return self -end -function VELOCITY:GetMiph() -return UTILS.MpsToMiph(self.Velocity) -end -function VELOCITY:GetText(Settings) -local Settings=Settings or _SETTINGS -if self.Velocity~=0 then -if Settings:IsMetric()then -return string.format("%d km/h",UTILS.MpsToKmph(self.Velocity)) -else -return string.format("%d mi/h",UTILS.MpsToMiph(self.Velocity)) -end -else -return"stationary" -end -end -function VELOCITY:ToString(VelocityGroup,Settings) -self:F({Group=VelocityGroup and VelocityGroup:GetName()}) -local Settings=Settings or(VelocityGroup and _DATABASE:GetPlayerSettings(VelocityGroup:GetPlayerName()))or _SETTINGS -return self:GetText(Settings) -end -end -do -VELOCITY_POSITIONABLE={ -ClassName="VELOCITY_POSITIONABLE", -} -function VELOCITY_POSITIONABLE:New(Positionable) -local self=BASE:Inherit(self,VELOCITY:New()) -self:F({}) -self.Positionable=Positionable -return self -end -function VELOCITY_POSITIONABLE:Get() -return self.Positionable:GetVelocityMPS()or 0 -end -function VELOCITY_POSITIONABLE:GetKmph() -return UTILS.MpsToKmph(self.Positionable:GetVelocityMPS()or 0) -end -function VELOCITY_POSITIONABLE:GetMiph() -return UTILS.MpsToMiph(self.Positionable:GetVelocityMPS()or 0) -end -function VELOCITY_POSITIONABLE:ToString() -self:F({Group=self.Positionable and self.Positionable:GetName()}) -local Settings=Settings or(self.Positionable and _DATABASE:GetPlayerSettings(self.Positionable:GetPlayerName()))or _SETTINGS -self.Velocity=self.Positionable:GetVelocityMPS() -return self:GetText(Settings) -end -end -MESSAGE={ -ClassName="MESSAGE", -MessageCategory=0, -MessageID=0, -} -MESSAGE.Type={ -Update="Update", -Information="Information", -Briefing="Briefing Report", -Overview="Overview Report", -Detailed="Detailed Report", -} -function MESSAGE:New(MessageText,MessageDuration,MessageCategory,ClearScreen) -local self=BASE:Inherit(self,BASE:New()) -self:F({MessageText,MessageDuration,MessageCategory}) -self.MessageType=nil -if MessageCategory and MessageCategory~=""then -if MessageCategory:sub(-1)~="\n"then -self.MessageCategory=MessageCategory..": " -else -self.MessageCategory=MessageCategory:sub(1,-2)..":\n" -end -else -self.MessageCategory="" -end -self.ClearScreen=false -if ClearScreen~=nil then -self.ClearScreen=ClearScreen -end -self.MessageDuration=MessageDuration or 5 -self.MessageTime=timer.getTime() -self.MessageText=MessageText:gsub("^\n","",1):gsub("\n$","",1) -self.MessageSent=false -self.MessageGroup=false -self.MessageCoalition=false -return self -end -function MESSAGE:NewType(MessageText,MessageType,ClearScreen) -local self=BASE:Inherit(self,BASE:New()) -self:F({MessageText}) -self.MessageType=MessageType -self.ClearScreen=false -if ClearScreen~=nil then -self.ClearScreen=ClearScreen -end -self.MessageTime=timer.getTime() -self.MessageText=MessageText:gsub("^\n","",1):gsub("\n$","",1) -return self -end -function MESSAGE:Clear() -self:F() -self.ClearScreen=true -return self -end -function MESSAGE:ToClient(Client,Settings) -self:F(Client) -if Client and Client:GetClientGroupID()then -if self.MessageType then -local Settings=Settings or(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS -self.MessageDuration=Settings:GetMessageTime(self.MessageType) -self.MessageCategory="" -end -local Unit=Client:GetClient() -if self.MessageDuration~=0 then -local ClientGroupID=Client:GetClientGroupID() -self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) -trigger.action.outTextForUnit(Unit:GetID(),self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) -end -end -return self -end -function MESSAGE:ToGroup(Group,Settings) -self:F(Group.GroupName) -if Group then -if self.MessageType then -local Settings=Settings or(Group and _DATABASE:GetPlayerSettings(Group:GetPlayerName()))or _SETTINGS -self.MessageDuration=Settings:GetMessageTime(self.MessageType) -self.MessageCategory="" -end -if self.MessageDuration~=0 then -self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) -trigger.action.outTextForGroup(Group:GetID(),self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) -end -end -return self -end -function MESSAGE:ToUnit(Unit,Settings) -self:F(Unit.IdentifiableName) -if Unit then -if self.MessageType then -local Settings=Settings or(Unit and _DATABASE:GetPlayerSettings(Unit:GetPlayerName()))or _SETTINGS -self.MessageDuration=Settings:GetMessageTime(self.MessageType) -self.MessageCategory="" -end -if self.MessageDuration~=0 then -self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) -trigger.action.outTextForUnit(Unit:GetID(),self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) -end -end -return self -end -function MESSAGE:ToCountry(Country,Settings) -self:F(Country) -if Country then -if self.MessageType then -local Settings=Settings or _SETTINGS -self.MessageDuration=Settings:GetMessageTime(self.MessageType) -self.MessageCategory="" -end -if self.MessageDuration~=0 then -self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) -trigger.action.outTextForCountry(Country,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) -end -end -return self -end -function MESSAGE:ToCountryIf(Country,Condition,Settings) -self:F(Country) -if Country and Condition==true then -self:ToCountry(Country,Settings) -end -return self -end -function MESSAGE:ToBlue() -self:F() -self:ToCoalition(coalition.side.BLUE) -return self -end -function MESSAGE:ToRed() -self:F() -self:ToCoalition(coalition.side.RED) -return self -end -function MESSAGE:ToCoalition(CoalitionSide,Settings) -self:F(CoalitionSide) -if self.MessageType then -local Settings=Settings or _SETTINGS -self.MessageDuration=Settings:GetMessageTime(self.MessageType) -self.MessageCategory="" -end -if CoalitionSide then -if self.MessageDuration~=0 then -self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) -trigger.action.outTextForCoalition(CoalitionSide,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) -end -end -self.CoalitionSide=CoalitionSide -return self -end -function MESSAGE:ToCoalitionIf(CoalitionSide,Condition) -self:F(CoalitionSide) -if Condition and Condition==true then -self:ToCoalition(CoalitionSide) -end -return self -end -function MESSAGE:ToAll(Settings) -self:F() -if self.MessageType then -local Settings=Settings or _SETTINGS -self.MessageDuration=Settings:GetMessageTime(self.MessageType) -self.MessageCategory="" -end -if self.MessageDuration~=0 then -self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) -trigger.action.outText(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) -end -return self -end -function MESSAGE:ToAllIf(Condition) -if Condition and Condition==true then -self:ToAll() -end -return self -end -function MESSAGE:ToLog() -env.info(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","")) -return self -end -function MESSAGE:ToLogIf(Condition) -if Condition and Condition==true then -env.info(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","")) -end -return self -end -_MESSAGESRS={} -function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate) -_MESSAGESRS.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone" -_MESSAGESRS.frequency=Frequency or MSRS.frequencies or 243 -_MESSAGESRS.modulation=Modulation or MSRS.modulations or radio.modulation.AM -_MESSAGESRS.MSRS=MSRS:New(_MESSAGESRS.PathToSRS,_MESSAGESRS.frequency,_MESSAGESRS.modulation) -_MESSAGESRS.coalition=Coalition or MSRS.coalition or coalition.side.NEUTRAL -_MESSAGESRS.MSRS:SetCoalition(_MESSAGESRS.coalition) -_MESSAGESRS.coordinate=Coordinate -if Coordinate then -_MESSAGESRS.MSRS:SetCoordinate(Coordinate) -end -_MESSAGESRS.Culture=Culture or MSRS.culture or"en-GB" -_MESSAGESRS.MSRS:SetCulture(Culture) -_MESSAGESRS.Gender=Gender or MSRS.gender or"female" -_MESSAGESRS.MSRS:SetGender(Gender) -if PathToCredentials then -_MESSAGESRS.MSRS:SetProviderOptionsGoogle(PathToCredentials) -_MESSAGESRS.MSRS:SetProvider(MSRS.Provider.GOOGLE) -end -_MESSAGESRS.label=Label or MSRS.Label or"MESSAGE" -_MESSAGESRS.MSRS:SetLabel(Label or"MESSAGE") -_MESSAGESRS.port=Port or MSRS.port or 5002 -_MESSAGESRS.MSRS:SetPort(Port or 5002) -_MESSAGESRS.volume=Volume or MSRS.volume or 1 -_MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume) -if Voice then _MESSAGESRS.MSRS:SetVoice(Voice)end -_MESSAGESRS.voice=Voice or MSRS.voice -_MESSAGESRS.SRSQ=MSRSQUEUE:New(_MESSAGESRS.label) -end -function MESSAGE:ToSRS(frequency,modulation,gender,culture,voice,coalition,volume,coordinate) -local tgender=gender or _MESSAGESRS.Gender -if _MESSAGESRS.SRSQ then -if voice then -_MESSAGESRS.MSRS:SetVoice(voice or _MESSAGESRS.voice) -end -if coordinate then -_MESSAGESRS.MSRS:SetCoordinate(coordinate) -end -local category=string.gsub(self.MessageCategory,":","") -_MESSAGESRS.SRSQ:NewTransmission(self.MessageText,nil,_MESSAGESRS.MSRS,0.5,1,nil,nil,nil,frequency or _MESSAGESRS.frequency,modulation or _MESSAGESRS.modulation,gender or _MESSAGESRS.Gender,culture or _MESSAGESRS.Culture,nil,volume or _MESSAGESRS.volume,category,coordinate or _MESSAGESRS.coordinate) -end -return self -end -function MESSAGE:ToSRSBlue(frequency,modulation,gender,culture,voice,volume,coordinate) -self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.BLUE,volume,coordinate) -return self -end -function MESSAGE:ToSRSRed(frequency,modulation,gender,culture,voice,volume,coordinate) -self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.RED,volume,coordinate) -return self -end -function MESSAGE:ToSRSAll(frequency,modulation,gender,culture,voice,volume,coordinate) -self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.NEUTRAL,volume,coordinate) -return self -end -do -FSM={ -ClassName="FSM", -} -function FSM:New() -self=BASE:Inherit(self,BASE:New()) -self.options=options or{} -self.options.subs=self.options.subs or{} -self.current=self.options.initial or'none' -self.Events={} -self.subs={} -self.endstates={} -self.Scores={} -self._StartState="none" -self._Transitions={} -self._Processes={} -self._EndStates={} -self._Scores={} -self._EventSchedules={} -self.CallScheduler=SCHEDULER:New(self) -return self -end -function FSM:SetStartState(State) -self._StartState=State -self.current=State -end -function FSM:GetStartState() -return self._StartState or{} -end -function FSM:AddTransition(From,Event,To) -local Transition={} -Transition.From=From -Transition.Event=Event -Transition.To=To -self._Transitions[Transition]=Transition -self:_eventmap(self.Events,Transition) -end -function FSM:GetTransitions() -return self._Transitions or{} -end -function FSM:AddProcess(From,Event,Process,ReturnEvents) -local Sub={} -Sub.From=From -Sub.Event=Event -Sub.fsm=Process -Sub.StartEvent="Start" -Sub.ReturnEvents=ReturnEvents -self._Processes[Sub]=Sub -self:_submap(self.subs,Sub,nil) -self:AddTransition(From,Event,From) -return Process -end -function FSM:GetProcesses() -self:F({Processes=self._Processes}) -return self._Processes or{} -end -function FSM:GetProcess(From,Event) -for ProcessID,Process in pairs(self:GetProcesses())do -if Process.From==From and Process.Event==Event then -return Process.fsm -end -end -error("Sub-Process from state "..From.." with event "..Event.." not found!") -end -function FSM:SetProcess(From,Event,Fsm) -for ProcessID,Process in pairs(self:GetProcesses())do -if Process.From==From and Process.Event==Event then -Process.fsm=Fsm -return true -end -end -error("Sub-Process from state "..From.." with event "..Event.." not found!") -end -function FSM:AddEndState(State) -self._EndStates[State]=State -self.endstates[State]=State -end -function FSM:GetEndStates() -return self._EndStates or{} -end -function FSM:AddScore(State,ScoreText,Score) -self:F({State,ScoreText,Score}) -self._Scores[State]=self._Scores[State]or{} -self._Scores[State].ScoreText=ScoreText -self._Scores[State].Score=Score -return self -end -function FSM:AddScoreProcess(From,Event,State,ScoreText,Score) -self:F({From,Event,State,ScoreText,Score}) -local Process=self:GetProcess(From,Event) -Process._Scores[State]=Process._Scores[State]or{} -Process._Scores[State].ScoreText=ScoreText -Process._Scores[State].Score=Score -return Process -end -function FSM:GetScores() -return self._Scores or{} -end -function FSM:GetSubs() -return self.options.subs -end -function FSM:LoadCallBacks(CallBackTable) -for name,callback in pairs(CallBackTable or{})do -self[name]=callback -end -end -function FSM:_eventmap(Events,EventStructure) -local Event=EventStructure.Event -local __Event="__"..EventStructure.Event -self[Event]=self[Event]or self:_create_transition(Event) -self[__Event]=self[__Event]or self:_delayed_transition(Event) -Events[Event]=self.Events[Event]or{map={}} -self:_add_to_map(Events[Event].map,EventStructure) -end -function FSM:_submap(subs,sub,name) -subs[sub.From]=subs[sub.From]or{} -subs[sub.From][sub.Event]=subs[sub.From][sub.Event]or{} -subs[sub.From][sub.Event][sub]={} -subs[sub.From][sub.Event][sub].fsm=sub.fsm -subs[sub.From][sub.Event][sub].StartEvent=sub.StartEvent -subs[sub.From][sub.Event][sub].ReturnEvents=sub.ReturnEvents or{} -subs[sub.From][sub.Event][sub].name=name -subs[sub.From][sub.Event][sub].fsmparent=self -end -function FSM:_call_handler(step,trigger,params,EventName) -local handler=step..trigger -if self[handler]then -self._EventSchedules[EventName]=nil -local ErrorHandler=function(errmsg) -env.info("Error in SCHEDULER function:"..errmsg) -if BASE.Debug~=nil then -env.info(BASE.Debug.traceback()) -end -return errmsg -end -local Result,Value=xpcall(function() -return self[handler](self,unpack(params)) -end,ErrorHandler) -return Value -end -end -function FSM._handler(self,EventName,...) -local Can,To=self:can(EventName) -if To=="*"then -To=self.current -end -if Can then -local From=self.current -local Params={From,EventName,To,...} -if self["onleave"..From]or -self["OnLeave"..From]or -self["onbefore"..EventName]or -self["OnBefore"..EventName]or -self["onafter"..EventName]or -self["OnAfter"..EventName]or -self["onenter"..To]or -self["OnEnter"..To]then -if self:_call_handler("onbefore",EventName,Params,EventName)==false then -self:T("*** FSM *** Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** onbefore"..EventName) -return false -else -if self:_call_handler("OnBefore",EventName,Params,EventName)==false then -self:T("*** FSM *** Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** OnBefore"..EventName) -return false -else -if self:_call_handler("onleave",From,Params,EventName)==false then -self:T("*** FSM *** Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** onleave"..From) -return false -else -if self:_call_handler("OnLeave",From,Params,EventName)==false then -self:T("*** FSM *** Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** OnLeave"..From) -return false -end -end -end -end -else -local ClassName=self:GetClassName() -if ClassName=="FSM"then -self:T("*** FSM *** Transit *** "..self.current.." --> "..EventName.." --> "..To) -end -if ClassName=="FSM_TASK"then -self:T("*** FSM *** Transit *** "..self.current.." --> "..EventName.." --> "..To.." *** Task: "..self.TaskName) -end -if ClassName=="FSM_CONTROLLABLE"then -self:T("*** FSM *** Transit *** "..self.current.." --> "..EventName.." --> "..To.." *** TaskUnit: "..self.Controllable.ControllableName.." *** ") -end -if ClassName=="FSM_PROCESS"then -self:T("*** FSM *** Transit *** "..self.current.." --> "..EventName.." --> "..To.." *** Task: "..self.Task:GetName()..", TaskUnit: "..self.Controllable.ControllableName.." *** ") -end -end -self.current=To -local execute=true -local subtable=self:_gosub(From,EventName) -for _,sub in pairs(subtable)do -self:T("*** FSM *** Sub *** "..sub.StartEvent) -sub.fsm.fsmparent=self -sub.fsm.ReturnEvents=sub.ReturnEvents -sub.fsm[sub.StartEvent](sub.fsm) -execute=false -end -local fsmparent,Event=self:_isendstate(To) -if fsmparent and Event then -self:T("*** FSM *** End *** "..Event) -self:_call_handler("onenter",To,Params,EventName) -self:_call_handler("OnEnter",To,Params,EventName) -self:_call_handler("onafter",EventName,Params,EventName) -self:_call_handler("OnAfter",EventName,Params,EventName) -self:_call_handler("onstate","change",Params,EventName) -fsmparent[Event](fsmparent) -execute=false -end -if execute then -self:_call_handler("onafter",EventName,Params,EventName) -self:_call_handler("OnAfter",EventName,Params,EventName) -self:_call_handler("onenter",To,Params,EventName) -self:_call_handler("OnEnter",To,Params,EventName) -self:_call_handler("onstate","change",Params,EventName) -end -else -self:T("*** FSM *** NO Transition *** "..self.current.." --> "..EventName.." --> ? ") -end -return nil -end -function FSM:_delayed_transition(EventName) -return function(self,DelaySeconds,...) -self:T3("Delayed Event: "..EventName) -local CallID=0 -if DelaySeconds~=nil then -if DelaySeconds<0 then -DelaySeconds=math.abs(DelaySeconds) -if not self._EventSchedules[EventName]then -CallID=self.CallScheduler:Schedule(self,self._handler,{EventName,...},DelaySeconds or 1,nil,nil,nil,4,true) -self._EventSchedules[EventName]=CallID -self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec SCHEDULED with CallID=%s",EventName,DelaySeconds,tostring(CallID))) -else -self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec CANCELLED as we already have such an event in the queue.",EventName,DelaySeconds)) -end -else -CallID=self.CallScheduler:Schedule(self,self._handler,{EventName,...},DelaySeconds or 1,nil,nil,nil,4,true) -self:T2(string.format("Event %s delayed by %.3f sec SCHEDULED with CallID=%s",EventName,DelaySeconds,tostring(CallID))) -end -else -error("FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this.") -end -end -end -function FSM:_create_transition(EventName) -return function(self,...) -return self._handler(self,EventName,...) -end -end -function FSM:_gosub(ParentFrom,ParentEvent) -local fsmtable={} -if self.subs[ParentFrom]and self.subs[ParentFrom][ParentEvent]then -return self.subs[ParentFrom][ParentEvent] -else -return{} -end -end -function FSM:_isendstate(Current) -local FSMParent=self.fsmparent -if FSMParent and self.endstates[Current]then -FSMParent.current=Current -local ParentFrom=FSMParent.current -local Event=self.ReturnEvents[Current] -if Event then -return FSMParent,Event -else -end -end -return nil -end -function FSM:_add_to_map(Map,Event) -self:F3({Map,Event}) -if type(Event.From)=='string'then -Map[Event.From]=Event.To -else -for _,From in ipairs(Event.From)do -Map[From]=Event.To -end -end -end -function FSM:GetState() -return self.current -end -function FSM:GetCurrentState() -return self.current -end -function FSM:Is(State) -return self.current==State -end -function FSM:is(state) -return self.current==state -end -function FSM:can(e) -local Event=self.Events[e] -local To=Event and Event.map[self.current]or Event.map['*'] -return To~=nil,To -end -function FSM:cannot(e) -return not self:can(e) -end -end -do -FSM_CONTROLLABLE={ -ClassName="FSM_CONTROLLABLE", -} -function FSM_CONTROLLABLE:New(Controllable) -local self=BASE:Inherit(self,FSM:New()) -if Controllable then -self:SetControllable(Controllable) -end -self:AddTransition("*","Stop","Stopped") -return self -end -function FSM_CONTROLLABLE:OnAfterStop(Controllable,From,Event,To) -self.CallScheduler:Clear() -end -function FSM_CONTROLLABLE:SetControllable(FSMControllable) -self.Controllable=FSMControllable -end -function FSM_CONTROLLABLE:GetControllable() -return self.Controllable -end -function FSM_CONTROLLABLE:_call_handler(step,trigger,params,EventName) -local handler=step..trigger -local ErrorHandler=function(errmsg) -env.info("Error in SCHEDULER function:"..errmsg) -if BASE.Debug~=nil then -env.info(BASE.Debug.traceback()) -end -return errmsg -end -if self[handler]then -self:T("*** FSM *** "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3].." *** TaskUnit: "..self.Controllable:GetName()) -self._EventSchedules[EventName]=nil -local Result,Value=xpcall(function() -return self[handler](self,self.Controllable,unpack(params)) -end,ErrorHandler) -return Value -end -end -end -do -FSM_PROCESS={ClassName="FSM_PROCESS"} -function FSM_PROCESS:New(Controllable,Task) -local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) -self:Assign(Controllable,Task) -return self -end -function FSM_PROCESS:Init(FsmProcess) -self:T("No Initialisation") -end -function FSM_PROCESS:_call_handler(step,trigger,params,EventName) -local handler=step..trigger -local ErrorHandler=function(errmsg) -env.info("Error in FSM_PROCESS call handler:"..errmsg) -if BASE.Debug~=nil then -env.info(BASE.Debug.traceback()) -end -return errmsg -end -if self[handler]then -if handler~="onstatechange"then -self:T("*** FSM *** "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3].." *** Task: "..self.Task:GetName()..", TaskUnit: "..self.Controllable:GetName()) -end -self._EventSchedules[EventName]=nil -local Result,Value -if self.Controllable and self.Controllable:IsAlive()==true then -Result,Value=xpcall(function() -return self[handler](self,self.Controllable,self.Task,unpack(params)) -end,ErrorHandler) -end -return Value -end -end -function FSM_PROCESS:Copy(Controllable,Task) -local NewFsm=self:New(Controllable,Task) -NewFsm:Assign(Controllable,Task) -NewFsm:Init(self) -NewFsm:SetStartState(self:GetStartState()) -for TransitionID,Transition in pairs(self:GetTransitions())do -NewFsm:AddTransition(Transition.From,Transition.Event,Transition.To) -end -for ProcessID,Process in pairs(self:GetProcesses())do -local FsmProcess=NewFsm:AddProcess(Process.From,Process.Event,Process.fsm:Copy(Controllable,Task),Process.ReturnEvents) -end -for EndStateID,EndState in pairs(self:GetEndStates())do -NewFsm:AddEndState(EndState) -end -for ScoreID,Score in pairs(self:GetScores())do -NewFsm:AddScore(ScoreID,Score.ScoreText,Score.Score) -end -return NewFsm -end -function FSM_PROCESS:Remove() -self:F({self:GetClassNameAndID()}) -self:F("Clearing Schedules") -self.CallScheduler:Clear() -for ProcessID,Process in pairs(self:GetProcesses())do -if Process.fsm then -Process.fsm:Remove() -Process.fsm=nil -end -end -return self -end -function FSM_PROCESS:SetTask(Task) -self.Task=Task -return self -end -function FSM_PROCESS:GetTask() -return self.Task -end -function FSM_PROCESS:GetMission() -return self.Task.Mission -end -function FSM_PROCESS:GetCommandCenter() -return self:GetTask():GetMission():GetCommandCenter() -end -function FSM_PROCESS:Message(Message) -self:F({Message=Message}) -local CC=self:GetCommandCenter() -local TaskGroup=self.Controllable:GetGroup() -local PlayerName=self.Controllable:GetPlayerName() -PlayerName=PlayerName and" ("..PlayerName..")"or"" -local Callsign=self.Controllable:GetCallsign() -local Prefix=Callsign and" @ "..Callsign..PlayerName or"" -Message=Prefix..": "..Message -CC:MessageToGroup(Message,TaskGroup) -end -function FSM_PROCESS:Assign(ProcessUnit,Task) -self:SetControllable(ProcessUnit) -self:SetTask(Task) -return self -end -function FSM_PROCESS:onenterFailed(ProcessUnit,Task,From,Event,To) -self:T("*** FSM *** Failed *** "..Task:GetName().."/"..ProcessUnit:GetName().." *** "..From.." --> "..Event.." --> "..To) -self.Task:Fail() -end -function FSM_PROCESS:onstatechange(ProcessUnit,Task,From,Event,To) -if From~=To then -self:T("*** FSM *** Change *** "..Task:GetName().."/"..ProcessUnit:GetName().." *** "..From.." --> "..Event.." --> "..To) -end -if self._Scores[To]then -local Task=self.Task -local Scoring=Task:GetScoring() -if Scoring then -Scoring:_AddMissionTaskScore(Task.Mission,ProcessUnit,self._Scores[To].ScoreText,self._Scores[To].Score) -end -end -end -end -do -FSM_TASK={ -ClassName="FSM_TASK", -} -function FSM_TASK:New(TaskName) -local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) -self["onstatechange"]=self.OnStateChange -self.TaskName=TaskName -return self -end -function FSM_TASK:_call_handler(step,trigger,params,EventName) -local handler=step..trigger -local ErrorHandler=function(errmsg) -env.info("Error in SCHEDULER function:"..errmsg) -if BASE.Debug~=nil then -env.info(BASE.Debug.traceback()) -end -return errmsg -end -if self[handler]then -self:T("*** FSM *** "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3].." *** Task: "..self.TaskName) -self._EventSchedules[EventName]=nil -local Result,Value=xpcall(function() -return self[handler](self,unpack(params)) -end,ErrorHandler) -return Value -end -end -end -do -FSM_SET={ -ClassName="FSM_SET", -} -function FSM_SET:New(FSMSet) -self=BASE:Inherit(self,FSM:New()) -if FSMSet then -self:Set(FSMSet) -end -return self -end -function FSM_SET:Set(FSMSet) -self:F(FSMSet) -self.Set=FSMSet -end -function FSM_SET:Get() -return self.Set -end -function FSM_SET:_call_handler(step,trigger,params,EventName) -local handler=step..trigger -if self[handler]then -self:T("*** FSM *** "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3]) -self._EventSchedules[EventName]=nil -return self[handler](self,self.Set,unpack(params)) -end -end -end -SPAWN={ -ClassName="SPAWN", -SpawnTemplatePrefix=nil, -SpawnAliasPrefix=nil, -} -SPAWN.Takeoff={ -Air=1, -Runway=2, -Hot=3, -Cold=4, -} -function SPAWN:New(SpawnTemplatePrefix) -local self=BASE:Inherit(self,BASE:New()) -self:F({SpawnTemplatePrefix}) -local TemplateGroup=GROUP:FindByName(SpawnTemplatePrefix) -if TemplateGroup then -self.SpawnTemplatePrefix=SpawnTemplatePrefix -self.SpawnIndex=0 -self.SpawnCount=0 -self.AliveUnits=0 -self.SpawnIsScheduled=false -self.SpawnTemplate=self._GetTemplate(self,SpawnTemplatePrefix) -self.Repeat=false -self.UnControlled=false -self.SpawnInitLimit=false -self.SpawnMaxUnitsAlive=0 -self.SpawnMaxGroups=0 -self.SpawnRandomize=false -self.SpawnVisible=false -self.AIOnOff=true -self.SpawnUnControlled=false -self.SpawnInitKeepUnitNames=false -self.DelayOnOff=false -self.SpawnGrouping=nil -self.SpawnInitLivery=nil -self.SpawnInitSkill=nil -self.SpawnInitFreq=nil -self.SpawnInitModu=nil -self.SpawnInitRadio=nil -self.SpawnInitModex=nil -self.SpawnInitModexPrefix=nil -self.SpawnInitModexPostfix=nil -self.SpawnInitAirbase=nil -self.TweakedTemplate=false -self.SpawnRandomCallsign=false -self.SpawnGroups={} -else -error("SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'") -end -self:SetEventPriority(5) -self.SpawnHookScheduler=SCHEDULER:New(nil) -return self -end -function SPAWN:NewWithAlias(SpawnTemplatePrefix,SpawnAliasPrefix) -local self=BASE:Inherit(self,BASE:New()) -self:F({SpawnTemplatePrefix,SpawnAliasPrefix}) -local TemplateGroup=GROUP:FindByName(SpawnTemplatePrefix) -if TemplateGroup then -self.SpawnTemplatePrefix=SpawnTemplatePrefix -self.SpawnAliasPrefix=SpawnAliasPrefix -self.SpawnIndex=0 -self.SpawnCount=0 -self.AliveUnits=0 -self.SpawnIsScheduled=false -self.SpawnTemplate=self._GetTemplate(self,SpawnTemplatePrefix) -self.Repeat=false -self.UnControlled=false -self.SpawnInitLimit=false -self.SpawnMaxUnitsAlive=0 -self.SpawnMaxGroups=0 -self.SpawnRandomize=false -self.SpawnVisible=false -self.AIOnOff=true -self.SpawnUnControlled=false -self.SpawnInitKeepUnitNames=false -self.DelayOnOff=false -self.SpawnGrouping=nil -self.SpawnInitLivery=nil -self.SpawnInitSkill=nil -self.SpawnInitFreq=nil -self.SpawnInitModu=nil -self.SpawnInitRadio=nil -self.SpawnInitModex=nil -self.SpawnInitModexPrefix=nil -self.SpawnInitModexPostfix=nil -self.SpawnInitAirbase=nil -self.TweakedTemplate=false -self.SpawnGroups={} -else -error("SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'") -end -self:SetEventPriority(5) -self.SpawnHookScheduler=SCHEDULER:New(nil) -return self -end -function SPAWN:NewFromTemplate(SpawnTemplate,SpawnTemplatePrefix,SpawnAliasPrefix,NoMooseNamingPostfix) -local self=BASE:Inherit(self,BASE:New()) -self:F({SpawnTemplate,SpawnTemplatePrefix,SpawnAliasPrefix}) -if SpawnTemplatePrefix==nil or SpawnTemplatePrefix==""then -BASE:I("ERROR: in function NewFromTemplate, required parameter SpawnTemplatePrefix is not set") -return nil -end -if SpawnTemplate then -self.SpawnTemplate=SpawnTemplate -self.SpawnTemplatePrefix=SpawnTemplatePrefix -self.SpawnAliasPrefix=SpawnAliasPrefix or SpawnTemplatePrefix -self.SpawnTemplate.name=SpawnTemplatePrefix -self.SpawnIndex=0 -self.SpawnCount=0 -self.AliveUnits=0 -self.SpawnIsScheduled=false -self.Repeat=false -self.UnControlled=false -self.SpawnInitLimit=false -self.SpawnMaxUnitsAlive=0 -self.SpawnMaxGroups=0 -self.SpawnRandomize=false -self.SpawnVisible=false -self.AIOnOff=true -self.SpawnUnControlled=false -self.SpawnInitKeepUnitNames=false -self.DelayOnOff=false -self.Grouping=nil -self.SpawnInitLivery=nil -self.SpawnInitSkill=nil -self.SpawnInitFreq=nil -self.SpawnInitModu=nil -self.SpawnInitRadio=nil -self.SpawnInitModex=nil -self.SpawnInitModexPrefix=nil -self.SpawnInitModexPostfix=nil -self.SpawnInitAirbase=nil -self.TweakedTemplate=true -self.MooseNameing=true -if NoMooseNamingPostfix==true then -self.MooseNameing=false -end -self.SpawnGroups={} -else -error("There is no template provided for SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'") -end -self:SetEventPriority(5) -self.SpawnHookScheduler=SCHEDULER:New(nil) -return self -end -function SPAWN:InitLimit(SpawnMaxUnitsAlive,SpawnMaxGroups) -self:F({self.SpawnTemplatePrefix,SpawnMaxUnitsAlive,SpawnMaxGroups}) -self.SpawnInitLimit=true -self.SpawnMaxUnitsAlive=SpawnMaxUnitsAlive -self.SpawnMaxGroups=SpawnMaxGroups -for SpawnGroupID=1,self.SpawnMaxGroups do -self:_InitializeSpawnGroups(SpawnGroupID) -end -return self -end -function SPAWN:InitKeepUnitNames(KeepUnitNames) -self:F() -self.SpawnInitKeepUnitNames=KeepUnitNames or true -return self -end -function SPAWN:InitLateActivated(LateActivated) -self:F() -self.LateActivated=LateActivated or true -return self -end -function SPAWN:InitAirbase(AirbaseName,Takeoff,TerminalType) -self:F() -self.SpawnInitAirbase=AIRBASE:FindByName(AirbaseName) -self.SpawnInitTakeoff=Takeoff or SPAWN.Takeoff.Hot -self.SpawnInitTerminalType=TerminalType -return self -end -function SPAWN:InitHeading(HeadingMin,HeadingMax) -self:F() -self.SpawnInitHeadingMin=HeadingMin -self.SpawnInitHeadingMax=HeadingMax -return self -end -function SPAWN:InitGroupHeading(HeadingMin,HeadingMax,unitVar) -self:F({HeadingMin=HeadingMin,HeadingMax=HeadingMax,unitVar=unitVar}) -self.SpawnInitGroupHeadingMin=HeadingMin -self.SpawnInitGroupHeadingMax=HeadingMax -self.SpawnInitGroupUnitVar=unitVar -return self -end -function SPAWN:InitCoalition(Coalition) -self:F({coalition=Coalition}) -self.SpawnInitCoalition=Coalition -return self -end -function SPAWN:InitCountry(Country) -self:F() -self.SpawnInitCountry=Country -return self -end -function SPAWN:InitCategory(Category) -self:F() -self.SpawnInitCategory=Category -return self -end -function SPAWN:InitLivery(Livery) -self:F({livery=Livery}) -self.SpawnInitLivery=Livery -return self -end -function SPAWN:InitSkill(Skill) -self:F({skill=Skill}) -if Skill:lower()=="average"then -self.SpawnInitSkill="Average" -elseif Skill:lower()=="good"then -self.SpawnInitSkill="Good" -elseif Skill:lower()=="excellent"then -self.SpawnInitSkill="Excellent" -elseif Skill:lower()=="random"then -self.SpawnInitSkill="Random" -else -self.SpawnInitSkill="High" -end -return self -end -function SPAWN:InitRadioCommsOnOff(switch) -self:F({switch=switch}) -self.SpawnInitRadio=switch or true -return self -end -function SPAWN:InitRadioFrequency(frequency) -self:F({frequency=frequency}) -self.SpawnInitFreq=frequency -return self -end -function SPAWN:InitRadioModulation(modulation) -self:F({modulation=modulation}) -if modulation and modulation:lower()=="fm"then -self.SpawnInitModu=radio.modulation.FM -else -self.SpawnInitModu=radio.modulation.AM -end -return self -end -function SPAWN:InitModex(modex,prefix,postfix) -if modex then -self.SpawnInitModex=tonumber(modex) -end -self.SpawnInitModexPrefix=prefix -self.SpawnInitModexPostfix=postfix -return self -end -function SPAWN:InitRandomizeRoute(SpawnStartPoint,SpawnEndPoint,SpawnRadius,SpawnHeight) -self:F({self.SpawnTemplatePrefix,SpawnStartPoint,SpawnEndPoint,SpawnRadius,SpawnHeight}) -self.SpawnRandomizeRoute=true -self.SpawnRandomizeRouteStartPoint=SpawnStartPoint -self.SpawnRandomizeRouteEndPoint=SpawnEndPoint -self.SpawnRandomizeRouteRadius=SpawnRadius -self.SpawnRandomizeRouteHeight=SpawnHeight -for GroupID=1,self.SpawnMaxGroups do -self:_RandomizeRoute(GroupID) -end -return self -end -function SPAWN:InitRandomizePosition(RandomizePosition,OuterRadius,InnerRadius) -self:F({self.SpawnTemplatePrefix,RandomizePosition,OuterRadius,InnerRadius}) -self.SpawnRandomizePosition=RandomizePosition or false -self.SpawnRandomizePositionOuterRadius=OuterRadius or 0 -self.SpawnRandomizePositionInnerRadius=InnerRadius or 0 -for GroupID=1,self.SpawnMaxGroups do -self:_RandomizeRoute(GroupID) -end -return self -end -function SPAWN:InitRandomizeUnits(RandomizeUnits,OuterRadius,InnerRadius) -self:F({self.SpawnTemplatePrefix,RandomizeUnits,OuterRadius,InnerRadius}) -self.SpawnRandomizeUnits=RandomizeUnits or false -self.SpawnOuterRadius=OuterRadius or 0 -self.SpawnInnerRadius=InnerRadius or 0 -for GroupID=1,self.SpawnMaxGroups do -self:_RandomizeRoute(GroupID) -end -return self -end -function SPAWN:InitSetUnitRelativePositions(Positions) -self:F({self.SpawnTemplatePrefix,Positions}) -self.SpawnUnitsWithRelativePositions=true -self.UnitsRelativePositions=Positions -return self -end -function SPAWN:InitSetUnitAbsolutePositions(Positions) -self:F({self.SpawnTemplatePrefix,Positions}) -self.SpawnUnitsWithAbsolutePositions=true -self.UnitsAbsolutePositions=Positions -return self -end -function SPAWN:InitRandomizeTemplate(SpawnTemplatePrefixTable) -self:F({self.SpawnTemplatePrefix,SpawnTemplatePrefixTable}) -local temptable={} -for _,_temp in pairs(SpawnTemplatePrefixTable)do -temptable[#temptable+1]=_temp -end -self.SpawnTemplatePrefixTable=UTILS.ShuffleTable(temptable) -self.SpawnRandomizeTemplate=true -for SpawnGroupID=1,self.SpawnMaxGroups do -self:_RandomizeTemplate(SpawnGroupID) -end -return self -end -function SPAWN:InitRandomizeTemplateSet(SpawnTemplateSet) -self:F({self.SpawnTemplatePrefix}) -local setnames=SpawnTemplateSet:GetSetNames() -self:InitRandomizeTemplate(setnames) -return self -end -function SPAWN:InitRandomizeTemplatePrefixes(SpawnTemplatePrefixes) -self:F({self.SpawnTemplatePrefix}) -local SpawnTemplateSet=SET_GROUP:New():FilterPrefixes(SpawnTemplatePrefixes):FilterOnce() -self:InitRandomizeTemplateSet(SpawnTemplateSet) -return self -end -function SPAWN:InitGrouping(Grouping) -self:F({self.SpawnTemplatePrefix,Grouping}) -self.SpawnGrouping=Grouping -return self -end -function SPAWN:InitRandomizeZones(SpawnZoneTable) -self:F({self.SpawnTemplatePrefix,SpawnZoneTable}) -local temptable={} -for _,_temp in pairs(SpawnZoneTable)do -temptable[#temptable+1]=_temp -end -self.SpawnZoneTable=UTILS.ShuffleTable(temptable) -self.SpawnRandomizeZones=true -for SpawnGroupID=1,self.SpawnMaxGroups do -self:_RandomizeZones(SpawnGroupID) -end -return self -end -function SPAWN:InitRandomizeCallsign() -self.SpawnRandomCallsign=true -return self -end -function SPAWN:InitCallSign(ID,Name,Minor,Major) -self.SpawnInitCallSign=true -self.SpawnInitCallSignID=ID or 1 -self.SpawnInitCallSignMinor=Minor or 1 -self.SpawnInitCallSignMajor=Major or 1 -self.SpawnInitCallSignName=string.lower(Name)or"enfield" -return self -end -function SPAWN:InitPositionCoordinate(Coordinate) -self:T({self.SpawnTemplatePrefix,Coordinate:GetVec2()}) -self:InitPositionVec2(Coordinate:GetVec2()) -return self -end -function SPAWN:InitPositionVec2(Vec2) -self:T({self.SpawnTemplatePrefix,Vec2}) -self.SpawnInitPosition=Vec2 -self.SpawnFromNewPosition=true -self:I("MaxGroups:"..self.SpawnMaxGroups) -for SpawnGroupID=1,self.SpawnMaxGroups do -self:_SetInitialPosition(SpawnGroupID) -end -return self -end -function SPAWN:InitRepeat() -self:F({self.SpawnTemplatePrefix,self.SpawnIndex}) -self.Repeat=true -self.RepeatOnEngineShutDown=false -self.RepeatOnLanding=true -return self -end -function SPAWN:InitRepeatOnLanding() -self:F({self.SpawnTemplatePrefix}) -self:InitRepeat() -self.RepeatOnEngineShutDown=false -self.RepeatOnLanding=true -return self -end -function SPAWN:InitRepeatOnEngineShutDown() -self:F({self.SpawnTemplatePrefix}) -self:InitRepeat() -self.RepeatOnEngineShutDown=true -self.RepeatOnLanding=false -return self -end -function SPAWN:InitCleanUp(SpawnCleanUpInterval) -self:F({self.SpawnTemplatePrefix,SpawnCleanUpInterval}) -self.SpawnCleanUpInterval=SpawnCleanUpInterval -self.SpawnCleanUpTimeStamps={} -local SpawnGroup,SpawnCursor=self:GetFirstAliveGroup() -self:T({"CleanUp Scheduler:",SpawnGroup}) -self.CleanUpScheduler=SCHEDULER:New(self,self._SpawnCleanUpScheduler,{},1,SpawnCleanUpInterval,0.2) -return self -end -function SPAWN:InitArray(SpawnAngle,SpawnWidth,SpawnDeltaX,SpawnDeltaY) -self:F({self.SpawnTemplatePrefix,SpawnAngle,SpawnWidth,SpawnDeltaX,SpawnDeltaY}) -self.SpawnVisible=true -local SpawnX=0 -local SpawnY=0 -local SpawnXIndex=0 -local SpawnYIndex=0 -for SpawnGroupID=1,self.SpawnMaxGroups do -self:T({SpawnX,SpawnY,SpawnXIndex,SpawnYIndex}) -self.SpawnGroups[SpawnGroupID].Visible=true -self.SpawnGroups[SpawnGroupID].Spawned=false -SpawnXIndex=SpawnXIndex+1 -if SpawnWidth and SpawnWidth~=0 then -if SpawnXIndex>=SpawnWidth then -SpawnXIndex=0 -SpawnYIndex=SpawnYIndex+1 -end -end -local SpawnRootX=self.SpawnGroups[SpawnGroupID].SpawnTemplate.x -local SpawnRootY=self.SpawnGroups[SpawnGroupID].SpawnTemplate.y -self:_TranslateRotate(SpawnGroupID,SpawnRootX,SpawnRootY,SpawnX,SpawnY,SpawnAngle) -self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation=true -self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible=true -self.SpawnGroups[SpawnGroupID].Visible=true -self:HandleEvent(EVENTS.Birth,self._OnBirth) -self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash) -self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash) -self:HandleEvent(EVENTS.RemoveUnit,self._OnDeadOrCrash) -if self.Repeat then -self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff) -self:HandleEvent(EVENTS.Land,self._OnLand) -end -if self.RepeatOnEngineShutDown then -self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutDown) -end -self.SpawnGroups[SpawnGroupID].Group=_DATABASE:Spawn(self.SpawnGroups[SpawnGroupID].SpawnTemplate) -SpawnX=SpawnXIndex*SpawnDeltaX -SpawnY=SpawnYIndex*SpawnDeltaY -end -return self -end -do -function SPAWN:InitAIOnOff(AIOnOff) -self.AIOnOff=AIOnOff -return self -end -function SPAWN:InitAIOn() -return self:InitAIOnOff(true) -end -function SPAWN:InitAIOff() -return self:InitAIOnOff(false) -end -end -do -function SPAWN:InitDelayOnOff(DelayOnOff) -self.DelayOnOff=DelayOnOff -return self -end -function SPAWN:InitDelayOn() -return self:InitDelayOnOff(true) -end -function SPAWN:InitDelayOff() -return self:InitDelayOnOff(false) -end -end -function SPAWN:Spawn() -self:F({self.SpawnTemplatePrefix,self.SpawnIndex,self.AliveUnits}) -if self.SpawnInitAirbase then -return self:SpawnAtAirbase(self.SpawnInitAirbase,self.SpawnInitTakeoff,nil,self.SpawnInitTerminalType) -else -return self:SpawnWithIndex(self.SpawnIndex+1) -end -end -function SPAWN:ReSpawn(SpawnIndex) -self:F({self.SpawnTemplatePrefix,SpawnIndex}) -if not SpawnIndex then -SpawnIndex=1 -end -local SpawnGroup=self:GetGroupFromIndex(SpawnIndex) -local WayPoints=SpawnGroup and SpawnGroup.WayPoints or nil -if SpawnGroup then -local SpawnDCSGroup=SpawnGroup:GetDCSObject() -if SpawnDCSGroup then -SpawnGroup:Destroy() -end -end -local SpawnGroup=self:SpawnWithIndex(SpawnIndex) -if SpawnGroup and WayPoints then -SpawnGroup:WayPointInitialize(WayPoints) -SpawnGroup:WayPointExecute(1,5) -end -if SpawnGroup.ReSpawnFunction then -SpawnGroup:ReSpawnFunction() -end -SpawnGroup:ResetEvents() -return SpawnGroup -end -function SPAWN:SetSpawnIndex(SpawnIndex) -self.SpawnIndex=SpawnIndex or 0 -end -function SPAWN:SpawnWithIndex(SpawnIndex,NoBirth) -self:F2({SpawnTemplatePrefix=self.SpawnTemplatePrefix,SpawnIndex=SpawnIndex,AliveUnits=self.AliveUnits,SpawnMaxGroups=self.SpawnMaxGroups}) -if self:_GetSpawnIndex(SpawnIndex)then -if self.SpawnFromNewPosition then -self:_SetInitialPosition(SpawnIndex) -end -if self.SpawnGroups[self.SpawnIndex].Visible then -self.SpawnGroups[self.SpawnIndex].Group:Activate() -else -local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate -local SpawnZone=self.SpawnGroups[self.SpawnIndex].SpawnZone -self:T(SpawnTemplate.name) -if SpawnTemplate then -local PointVec3=POINT_VEC3:New(SpawnTemplate.route.points[1].x,SpawnTemplate.route.points[1].alt,SpawnTemplate.route.points[1].y) -self:T({"Current point of ",self.SpawnTemplatePrefix,PointVec3}) -if self.SpawnRandomizePosition then -local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnRandomizePositionOuterRadius,self.SpawnRandomizePositionInnerRadius) -local CurrentX=SpawnTemplate.units[1].x -local CurrentY=SpawnTemplate.units[1].y -SpawnTemplate.x=RandomVec2.x -SpawnTemplate.y=RandomVec2.y -for UnitID=1,#SpawnTemplate.units do -SpawnTemplate.units[UnitID].x=SpawnTemplate.units[UnitID].x+(RandomVec2.x-CurrentX) -SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-CurrentY) -self:T('SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) -end -end -if self.SpawnRandomizeUnits then -for UnitID=1,#SpawnTemplate.units do -local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) -if(SpawnZone)then -local inZone=SpawnZone:IsVec2InZone(RandomVec2) -local numTries=1 -while(not inZone)and(numTries<20)do -if not inZone then -RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) -numTries=numTries+1 -inZone=SpawnZone:IsVec2InZone(RandomVec2) -self:I("Retrying "..numTries.."spawn "..SpawnTemplate.name.." in Zone "..SpawnZone:GetName().."!") -self:I(SpawnZone) -end -end -if(not inZone)then -self:I("Could not place unit within zone and within radius!") -RandomVec2=SpawnZone:GetRandomVec2() -end -end -SpawnTemplate.units[UnitID].x=RandomVec2.x -SpawnTemplate.units[UnitID].y=RandomVec2.y -self:T('SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) -end -end -local function _Heading(courseDeg) -local h -if courseDeg<=180 then -h=math.rad(courseDeg) -else -h=-math.rad(360-courseDeg) -end -return h -end -local Rad180=math.rad(180) -local function _HeadingRad(courseRad) -if courseRad<=Rad180 then -return courseRad -else -return-((2*Rad180)-courseRad) -end -end -local function _RandomInRange(min,max) -if min and max then -return min+(math.random()*(max-min)) -else -return min -end -end -if self.SpawnInitGroupHeadingMin and#SpawnTemplate.units>0 then -local pivotX=SpawnTemplate.units[1].x -local pivotY=SpawnTemplate.units[1].y -local headingRad=math.rad(_RandomInRange(self.SpawnInitGroupHeadingMin or 0,self.SpawnInitGroupHeadingMax)) -local cosHeading=math.cos(headingRad) -local sinHeading=math.sin(headingRad) -local unitVarRad=math.rad(self.SpawnInitGroupUnitVar or 0) -for UnitID=1,#SpawnTemplate.units do -if not self.SpawnRandomizeUnits then -if UnitID>1 then -local unitXOff=SpawnTemplate.units[UnitID].x-pivotX -local unitYOff=SpawnTemplate.units[UnitID].y-pivotY -SpawnTemplate.units[UnitID].x=pivotX+(unitXOff*cosHeading)-(unitYOff*sinHeading) -SpawnTemplate.units[UnitID].y=pivotY+(unitYOff*cosHeading)+(unitXOff*sinHeading) -end -end -local unitHeading=SpawnTemplate.units[UnitID].heading+headingRad -SpawnTemplate.units[UnitID].heading=_HeadingRad(_RandomInRange(unitHeading-unitVarRad,unitHeading+unitVarRad)) -SpawnTemplate.units[UnitID].psi=-SpawnTemplate.units[UnitID].heading -end -end -if self.SpawnInitHeadingMin then -for UnitID=1,#SpawnTemplate.units do -SpawnTemplate.units[UnitID].heading=_Heading(_RandomInRange(self.SpawnInitHeadingMin,self.SpawnInitHeadingMax)) -SpawnTemplate.units[UnitID].psi=-SpawnTemplate.units[UnitID].heading -end -end -if self.SpawnUnitsWithRelativePositions and self.UnitsRelativePositions then -local BaseX=SpawnTemplate.units[1].x or 0 -local BaseY=SpawnTemplate.units[1].y or 0 -local BaseZ=SpawnTemplate.units[1].z or 0 -for UnitID=1,#SpawnTemplate.units do -if self.UnitsRelativePositions[UnitID].heading then -SpawnTemplate.units[UnitID].heading=math.rad(self.UnitsRelativePositions[UnitID].heading or 0) -end -SpawnTemplate.units[UnitID].x=BaseX+(self.UnitsRelativePositions[UnitID].x or 0) -SpawnTemplate.units[UnitID].y=BaseY+(self.UnitsRelativePositions[UnitID].y or 0) -if self.UnitsRelativePositions[UnitID].z then -SpawnTemplate.units[UnitID].z=BaseZ+(self.UnitsRelativePositions[UnitID].z or 0) -end -end -end -if self.SpawnUnitsWithAbsolutePositions and self.UnitsAbsolutePositions then -for UnitID=1,#SpawnTemplate.units do -if self.UnitsAbsolutePositions[UnitID].heading then -SpawnTemplate.units[UnitID].heading=math.rad(self.UnitsAbsolutePositions[UnitID].heading or 0) -end -SpawnTemplate.units[UnitID].x=self.UnitsAbsolutePositions[UnitID].x or 0 -SpawnTemplate.units[UnitID].y=self.UnitsAbsolutePositions[UnitID].y or 0 -if self.UnitsAbsolutePositions[UnitID].z then -SpawnTemplate.units[UnitID].z=self.UnitsAbsolutePositions[UnitID].z or 0 -end -end -end -if self.SpawnInitLivery then -for UnitID=1,#SpawnTemplate.units do -SpawnTemplate.units[UnitID].livery_id=self.SpawnInitLivery -end -end -if self.SpawnInitSkill then -for UnitID=1,#SpawnTemplate.units do -SpawnTemplate.units[UnitID].skill=self.SpawnInitSkill -end -end -if self.SpawnInitModex then -for UnitID=1,#SpawnTemplate.units do -local modexnumber=string.format("%03d",self.SpawnInitModex+(UnitID-1)) -if self.SpawnInitModexPrefix then modexnumber=self.SpawnInitModexPrefix..modexnumber end -if self.SpawnInitModexPostfix then modexnumber=modexnumber..self.SpawnInitModexPostfix end -SpawnTemplate.units[UnitID].onboard_num=modexnumber -end -end -if self.SpawnInitRadio then -SpawnTemplate.communication=self.SpawnInitRadio -end -if self.SpawnInitFreq then -SpawnTemplate.frequency=self.SpawnInitFreq -end -if self.SpawnInitModu then -SpawnTemplate.modulation=self.SpawnInitModu -end -SpawnTemplate.CategoryID=self.SpawnInitCategory or SpawnTemplate.CategoryID -SpawnTemplate.CountryID=self.SpawnInitCountry or SpawnTemplate.CountryID -SpawnTemplate.CoalitionID=self.SpawnInitCoalition or SpawnTemplate.CoalitionID -end -if not NoBirth then -self:HandleEvent(EVENTS.Birth,self._OnBirth) -end -self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash) -self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash) -self:HandleEvent(EVENTS.RemoveUnit,self._OnDeadOrCrash) -if self.Repeat then -self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff) -self:HandleEvent(EVENTS.Land,self._OnLand) -end -if self.RepeatOnEngineShutDown then -self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutDown) -end -self.SpawnGroups[self.SpawnIndex].Group=_DATABASE:Spawn(SpawnTemplate) -local SpawnGroup=self.SpawnGroups[self.SpawnIndex].Group -if SpawnGroup then -SpawnGroup:SetAIOnOff(self.AIOnOff) -end -self:T3(SpawnTemplate.name) -if self.SpawnFunctionHook then -self.SpawnHookScheduler:Schedule(nil,self.SpawnFunctionHook,{self.SpawnGroups[self.SpawnIndex].Group,unpack(self.SpawnFunctionArguments)},0.3) -end -end -self.SpawnGroups[self.SpawnIndex].Spawned=true -return self.SpawnGroups[self.SpawnIndex].Group -else -end -return nil -end -function SPAWN:SpawnScheduled(SpawnTime,SpawnTimeVariation,WithDelay) -self:F({SpawnTime,SpawnTimeVariation}) -local SpawnTime=SpawnTime or 60 -local SpawnTimeVariation=SpawnTimeVariation or 0.5 -if SpawnTime~=nil and SpawnTimeVariation~=nil then -local InitialDelay=0 -if WithDelay or self.DelayOnOff==true then -InitialDelay=math.random(SpawnTime-SpawnTime*SpawnTimeVariation,SpawnTime+SpawnTime*SpawnTimeVariation) -end -self.SpawnScheduler=SCHEDULER:New(self,self._Scheduler,{},InitialDelay,SpawnTime,SpawnTimeVariation) -end -return self -end -function SPAWN:SpawnScheduleStart() -self:F({self.SpawnTemplatePrefix}) -self.SpawnScheduler:Start() -return self -end -function SPAWN:SpawnScheduleStop() -self:F({self.SpawnTemplatePrefix}) -self.SpawnScheduler:Stop() -return self -end -function SPAWN:OnSpawnGroup(SpawnCallBackFunction,...) -self:F("OnSpawnGroup") -self.SpawnFunctionHook=SpawnCallBackFunction -self.SpawnFunctionArguments={} -if arg then -self.SpawnFunctionArguments=arg -end -return self -end -function SPAWN:SpawnAtAirbase(SpawnAirbase,Takeoff,TakeoffAltitude,TerminalType,EmergencyAirSpawn,Parkingdata) -self:F({self.SpawnTemplatePrefix,SpawnAirbase,Takeoff,TakeoffAltitude,TerminalType}) -local PointVec3=SpawnAirbase:GetCoordinate() -self:T2(PointVec3) -Takeoff=Takeoff or SPAWN.Takeoff.Hot -if EmergencyAirSpawn==nil then -EmergencyAirSpawn=true -end -self:F({SpawnIndex=self.SpawnIndex}) -if self:_GetSpawnIndex(self.SpawnIndex+1)then -local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate -self:F({SpawnTemplate=SpawnTemplate}) -if SpawnTemplate then -local GroupAlive=self:GetGroupFromIndex(self.SpawnIndex) -self:F({GroupAlive=GroupAlive}) -self:T({"Current point of ",self.SpawnTemplatePrefix,SpawnAirbase}) -local TemplateGroup=GROUP:FindByName(self.SpawnTemplatePrefix) -local TemplateUnit=TemplateGroup:GetUnit(1) -local group=TemplateGroup -local istransport=group:HasAttribute("Transports")and group:HasAttribute("Planes") -local isawacs=group:HasAttribute("AWACS") -local isfighter=group:HasAttribute("Fighters")or group:HasAttribute("Interceptors")or group:HasAttribute("Multirole fighters")or(group:HasAttribute("Bombers")and not group:HasAttribute("Strategic bombers")) -local isbomber=group:HasAttribute("Strategic bombers") -local istanker=group:HasAttribute("Tankers") -local ishelo=TemplateUnit:HasAttribute("Helicopters") -local nunits=#SpawnTemplate.units -local SpawnPoint=SpawnTemplate.route.points[1] -SpawnPoint.linkUnit=nil -SpawnPoint.helipadId=nil -SpawnPoint.airdromeId=nil -local AirbaseID=SpawnAirbase:GetID() -local AirbaseCategory=SpawnAirbase:GetAirbaseCategory() -self:F({AirbaseCategory=AirbaseCategory}) -if AirbaseCategory==Airbase.Category.SHIP then -SpawnPoint.linkUnit=AirbaseID -SpawnPoint.helipadId=AirbaseID -elseif AirbaseCategory==Airbase.Category.HELIPAD then -SpawnPoint.linkUnit=AirbaseID -SpawnPoint.helipadId=AirbaseID -elseif AirbaseCategory==Airbase.Category.AIRDROME then -SpawnPoint.airdromeId=AirbaseID -end -SpawnPoint.alt=0 -SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1] -SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2] -local spawnonground=not(Takeoff==SPAWN.Takeoff.Air) -self:T({spawnonground=spawnonground,TOtype=Takeoff,TOair=Takeoff==SPAWN.Takeoff.Air}) -local spawnonship=false -local spawnonfarp=false -local spawnonrunway=false -local spawnonairport=false -if spawnonground then -if AirbaseCategory==Airbase.Category.SHIP then -spawnonship=true -elseif AirbaseCategory==Airbase.Category.HELIPAD then -spawnonfarp=true -elseif AirbaseCategory==Airbase.Category.AIRDROME then -spawnonairport=true -end -spawnonrunway=Takeoff==SPAWN.Takeoff.Runway -end -local parkingspots={} -local parkingindex={} -local spots -if spawnonground and not SpawnTemplate.parked then -local nfree=0 -local termtype=TerminalType -if spawnonrunway then -if spawnonship then -if ishelo then -termtype=AIRBASE.TerminalType.HelicopterUsable -else -termtype=AIRBASE.TerminalType.OpenMedOrBig -end -else -termtype=AIRBASE.TerminalType.Runway -end -end -local scanradius=50 -local scanunits=true -local scanstatics=true -local scanscenery=false -local verysafe=false -if spawnonship or spawnonfarp or spawnonrunway then -self:T(string.format("Group %s is spawned on farp/ship/runway %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) -nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype,true) -spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype,true) -else -if ishelo then -if termtype==nil then -self:T(string.format("Helo group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.HelicopterOnly)) -spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata) -nfree=#spots -if nfree=1 then -for i=1,nunits do -table.insert(parkingspots,spots[1].Coordinate) -table.insert(parkingindex,spots[1].TerminalID) -end -PointVec3=spots[1].Coordinate -else -_notenough=true -end -elseif spawnonairport then -if nfree>=nunits then -for i=1,nunits do -table.insert(parkingspots,spots[i].Coordinate) -table.insert(parkingindex,spots[i].TerminalID) -end -else -_notenough=true -end -end -if _notenough then -if EmergencyAirSpawn and not self.SpawnUnControlled then -self:E(string.format("WARNING: Group %s has no parking spots at %s ==> air start!",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) -spawnonground=false -spawnonship=false -spawnonfarp=false -spawnonrunway=false -SpawnPoint.type=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -SpawnPoint.action=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] -PointVec3.x=PointVec3.x+math.random(-500,500) -PointVec3.z=PointVec3.z+math.random(-500,500) -if ishelo then -PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) -else -PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) -end -Takeoff=GROUP.Takeoff.Air -else -self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) -return nil -end -end -else -if TakeoffAltitude then -PointVec3.y=TakeoffAltitude -else -if ishelo then -PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) -else -PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) -end -end -end -if not SpawnTemplate.parked then -SpawnTemplate.parked=true -for UnitID=1,nunits do -self:T2('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) -local UnitTemplate=SpawnTemplate.units[UnitID] -local SX=UnitTemplate.x -local SY=UnitTemplate.y -local BX=SpawnTemplate.route.points[1].x -local BY=SpawnTemplate.route.points[1].y -local TX=PointVec3.x+(SX-BX) -local TY=PointVec3.z+(SY-BY) -if spawnonground then -if spawnonship or spawnonfarp or spawnonrunway then -self:T(string.format("Group %s spawning at farp, ship or runway %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) -SpawnTemplate.units[UnitID].x=PointVec3.x -SpawnTemplate.units[UnitID].y=PointVec3.z -SpawnTemplate.units[UnitID].alt=PointVec3.y -else -self:T(string.format("Group %s spawning at airbase %s on parking spot id %d",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),parkingindex[UnitID])) -SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x -SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z -SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y -end -else -self:T(string.format("Group %s spawning in air at %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) -SpawnTemplate.units[UnitID].x=TX -SpawnTemplate.units[UnitID].y=TY -SpawnTemplate.units[UnitID].alt=PointVec3.y -end -UnitTemplate.parking=nil -UnitTemplate.parking_id=nil -if parkingindex[UnitID]then -UnitTemplate.parking=parkingindex[UnitID] -end -self:T(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix,UnitID,tostring(UnitTemplate.parking))) -self:T(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix,UnitID,tostring(UnitTemplate.parking_id))) -self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) -end -end -SpawnPoint.x=PointVec3.x -SpawnPoint.y=PointVec3.z -SpawnPoint.alt=PointVec3.y -SpawnTemplate.x=PointVec3.x -SpawnTemplate.y=PointVec3.z -SpawnTemplate.uncontrolled=self.SpawnUnControlled -local GroupSpawned=self:SpawnWithIndex(self.SpawnIndex) -if Takeoff==GROUP.Takeoff.Air then -for UnitID,UnitSpawned in pairs(GroupSpawned:GetUnits())do -SCHEDULER:New(nil,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()},5) -end -end -if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then -SCHEDULER:New(nil,AIRBASE.CheckOnRunWay,{SpawnAirbase,GroupSpawned,75,true},1.0) -end -return GroupSpawned -end -end -return nil -end -function SPAWN:SpawnAtParkingSpot(Airbase,Spots,Takeoff) -self:F({Airbase=Airbase,Spots=Spots,Takeoff=Takeoff}) -if type(Spots)~="table"then -Spots={Spots} -end -if type(Airbase)=="string"then -Airbase=AIRBASE:FindByName(Airbase) -end -local group=GROUP:FindByName(self.SpawnTemplatePrefix) -local nunits=self.SpawnGrouping or#group:GetUnits() -if nunits then -if#Spots=nunits then -return self:SpawnAtAirbase(Airbase,Takeoff,nil,nil,nil,Parkingdata) -else -self:E("ERROR: Could not find enough free parking spots!") -end -else -self:E("ERROR: Could not get number of units in group!") -end -return nil -end -function SPAWN:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,SpawnIndex) -self:F({SpawnIndex=SpawnIndex,SpawnMaxGroups=self.SpawnMaxGroups}) -local PointVec3=SpawnAirbase:GetCoordinate() -self:T2(PointVec3) -local Takeoff=SPAWN.Takeoff.Cold -local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate -if SpawnTemplate then -local GroupAlive=self:GetGroupFromIndex(SpawnIndex) -self:T({"Current point of ",self.SpawnTemplatePrefix,SpawnAirbase}) -local TemplateGroup=GROUP:FindByName(self.SpawnTemplatePrefix) -local TemplateUnit=TemplateGroup:GetUnit(1) -local ishelo=TemplateUnit:HasAttribute("Helicopters") -local isbomber=TemplateUnit:HasAttribute("Bombers") -local istransport=TemplateUnit:HasAttribute("Transports") -local isfighter=TemplateUnit:HasAttribute("Battleplanes") -local nunits=#SpawnTemplate.units -local SpawnPoint=SpawnTemplate.route.points[1] -SpawnPoint.linkUnit=nil -SpawnPoint.helipadId=nil -SpawnPoint.airdromeId=nil -local AirbaseID=SpawnAirbase:GetID() -local AirbaseCategory=SpawnAirbase:GetAirbaseCategory() -self:F({AirbaseCategory=AirbaseCategory}) -if AirbaseCategory==Airbase.Category.SHIP then -SpawnPoint.linkUnit=AirbaseID -SpawnPoint.helipadId=AirbaseID -elseif AirbaseCategory==Airbase.Category.HELIPAD then -SpawnPoint.linkUnit=AirbaseID -SpawnPoint.helipadId=AirbaseID -elseif AirbaseCategory==Airbase.Category.AIRDROME then -SpawnPoint.airdromeId=AirbaseID -end -SpawnPoint.alt=0 -SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1] -SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2] -local spawnonground=not(Takeoff==SPAWN.Takeoff.Air) -self:T({spawnonground=spawnonground,TOtype=Takeoff,TOair=Takeoff==SPAWN.Takeoff.Air}) -local spawnonship=false -local spawnonfarp=false -local spawnonrunway=false -local spawnonairport=false -if spawnonground then -if AirbaseCategory==Airbase.Category.SHIP then -spawnonship=true -elseif AirbaseCategory==Airbase.Category.HELIPAD then -spawnonfarp=true -elseif AirbaseCategory==Airbase.Category.AIRDROME then -spawnonairport=true -end -spawnonrunway=Takeoff==SPAWN.Takeoff.Runway -end -local parkingspots={} -local parkingindex={} -local spots -if spawnonground and not SpawnTemplate.parked then -local nfree=0 -local termtype=TerminalType -local scanradius=50 -local scanunits=true -local scanstatics=true -local scanscenery=false -local verysafe=false -if spawnonship or spawnonfarp or spawnonrunway then -self:T(string.format("Group %s is spawned on farp/ship/runway %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) -nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype,true) -spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype,true) -else -if ishelo then -if termtype==nil then -self:T(string.format("Helo group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.HelicopterOnly)) -spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata) -nfree=#spots -if nfree=1 then -for i=1,nunits do -table.insert(parkingspots,spots[1].Coordinate) -table.insert(parkingindex,spots[1].TerminalID) -end -PointVec3=spots[1].Coordinate -else -_notenough=true -end -elseif spawnonairport then -if nfree>=nunits then -for i=1,nunits do -table.insert(parkingspots,spots[i].Coordinate) -table.insert(parkingindex,spots[i].TerminalID) -end -else -_notenough=true -end -end -if _notenough then -if not self.SpawnUnControlled then -else -self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) -return nil -end -end -else -end -if not SpawnTemplate.parked then -SpawnTemplate.parked=true -for UnitID=1,nunits do -self:F('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) -local UnitTemplate=SpawnTemplate.units[UnitID] -local SX=UnitTemplate.x -local SY=UnitTemplate.y -local BX=SpawnTemplate.route.points[1].x -local BY=SpawnTemplate.route.points[1].y -local TX=PointVec3.x+(SX-BX) -local TY=PointVec3.z+(SY-BY) -if spawnonground then -if spawnonship or spawnonfarp or spawnonrunway then -self:T(string.format("Group %s spawning at farp, ship or runway %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) -SpawnTemplate.units[UnitID].x=PointVec3.x -SpawnTemplate.units[UnitID].y=PointVec3.z -SpawnTemplate.units[UnitID].alt=PointVec3.y -else -self:T(string.format("Group %s spawning at airbase %s on parking spot id %d",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),parkingindex[UnitID])) -SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x -SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z -SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y -end -else -self:T(string.format("Group %s spawning in air at %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) -SpawnTemplate.units[UnitID].x=TX -SpawnTemplate.units[UnitID].y=TY -SpawnTemplate.units[UnitID].alt=PointVec3.y -end -UnitTemplate.parking=nil -UnitTemplate.parking_id=nil -if parkingindex[UnitID]then -UnitTemplate.parking=parkingindex[UnitID] -end -self:T2(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix,UnitID,tostring(UnitTemplate.parking))) -self:T2(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix,UnitID,tostring(UnitTemplate.parking_id))) -self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) -end -end -SpawnPoint.x=PointVec3.x -SpawnPoint.y=PointVec3.z -SpawnPoint.alt=PointVec3.y -SpawnTemplate.x=PointVec3.x -SpawnTemplate.y=PointVec3.z -SpawnTemplate.uncontrolled=true -local GroupSpawned=self:SpawnWithIndex(SpawnIndex,true) -if Takeoff==GROUP.Takeoff.Air then -for UnitID,UnitSpawned in pairs(GroupSpawned:GetUnits())do -SCHEDULER:New(nil,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()},5) -end -end -if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then -SCHEDULER:New(nil,AIRBASE.CheckOnRunWay,{SpawnAirbase,GroupSpawned,75,true},1.0) -end -end -end -function SPAWN:ParkAtAirbase(SpawnAirbase,TerminalType,Parkingdata) -self:F({self.SpawnTemplatePrefix,SpawnAirbase,TerminalType}) -self:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,1) -for SpawnIndex=2,self.SpawnMaxGroups do -self:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,SpawnIndex) -end -self:SetSpawnIndex(0) -return nil -end -function SPAWN:SpawnFromVec3(Vec3,SpawnIndex) -self:F({self.SpawnTemplatePrefix,Vec3,SpawnIndex}) -local PointVec3=POINT_VEC3:NewFromVec3(Vec3) -self:T2(PointVec3) -if SpawnIndex then -else -SpawnIndex=self.SpawnIndex+1 -end -if self:_GetSpawnIndex(SpawnIndex)then -local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate -if SpawnTemplate then -self:T({"Current point of ",self.SpawnTemplatePrefix,Vec3}) -local TemplateHeight=SpawnTemplate.route and SpawnTemplate.route.points[1].alt or nil -SpawnTemplate.route=SpawnTemplate.route or{} -SpawnTemplate.route.points=SpawnTemplate.route.points or{} -SpawnTemplate.route.points[1]=SpawnTemplate.route.points[1]or{} -SpawnTemplate.route.points[1].x=SpawnTemplate.route.points[1].x or 0 -SpawnTemplate.route.points[1].y=SpawnTemplate.route.points[1].y or 0 -for UnitID=1,#SpawnTemplate.units do -local UnitTemplate=SpawnTemplate.units[UnitID] -local SX=UnitTemplate.x or 0 -local SY=UnitTemplate.y or 0 -local BX=SpawnTemplate.route.points[1].x -local BY=SpawnTemplate.route.points[1].y -local TX=Vec3.x+(SX-BX) -local TY=Vec3.z+(SY-BY) -SpawnTemplate.units[UnitID].x=TX -SpawnTemplate.units[UnitID].y=TY -if SpawnTemplate.CategoryID~=Group.Category.SHIP then -SpawnTemplate.units[UnitID].alt=Vec3.y or TemplateHeight -end -self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) -end -SpawnTemplate.route.points[1].x=Vec3.x -SpawnTemplate.route.points[1].y=Vec3.z -if SpawnTemplate.CategoryID~=Group.Category.SHIP then -SpawnTemplate.route.points[1].alt=Vec3.y or TemplateHeight -end -SpawnTemplate.x=Vec3.x -SpawnTemplate.y=Vec3.z -SpawnTemplate.alt=Vec3.y or TemplateHeight -return self:SpawnWithIndex(self.SpawnIndex) -end -end -return nil -end -function SPAWN:SpawnFromCoordinate(Coordinate,SpawnIndex) -self:F({self.SpawnTemplatePrefix,SpawnIndex}) -return self:SpawnFromVec3(Coordinate:GetVec3(),SpawnIndex) -end -function SPAWN:SpawnFromPointVec3(PointVec3,SpawnIndex) -self:F({self.SpawnTemplatePrefix,SpawnIndex}) -return self:SpawnFromVec3(PointVec3:GetVec3(),SpawnIndex) -end -function SPAWN:SpawnFromVec2(Vec2,MinHeight,MaxHeight,SpawnIndex) -self:F({self.SpawnTemplatePrefix,self.SpawnIndex,Vec2,MinHeight,MaxHeight,SpawnIndex}) -local Height=nil -if MinHeight and MaxHeight then -Height=math.random(MinHeight,MaxHeight) -end -return self:SpawnFromVec3({x=Vec2.x,y=Height,z=Vec2.y},SpawnIndex) -end -function SPAWN:SpawnFromPointVec2(PointVec2,MinHeight,MaxHeight,SpawnIndex) -self:F({self.SpawnTemplatePrefix,self.SpawnIndex}) -return self:SpawnFromVec2(PointVec2:GetVec2(),MinHeight,MaxHeight,SpawnIndex) -end -function SPAWN:SpawnFromUnit(HostUnit,MinHeight,MaxHeight,SpawnIndex) -self:F({self.SpawnTemplatePrefix,HostUnit,MinHeight,MaxHeight,SpawnIndex}) -if HostUnit and HostUnit:IsAlive()~=nil then -return self:SpawnFromVec2(HostUnit:GetVec2(),MinHeight,MaxHeight,SpawnIndex) -end -return nil -end -function SPAWN:SpawnFromStatic(HostStatic,MinHeight,MaxHeight,SpawnIndex) -self:F({self.SpawnTemplatePrefix,HostStatic,MinHeight,MaxHeight,SpawnIndex}) -if HostStatic and HostStatic:IsAlive()then -return self:SpawnFromVec2(HostStatic:GetVec2(),MinHeight,MaxHeight,SpawnIndex) -end -return nil -end -function SPAWN:SpawnInZone(Zone,RandomizeGroup,MinHeight,MaxHeight,SpawnIndex) -self:F({self.SpawnTemplatePrefix,Zone,RandomizeGroup,MinHeight,MaxHeight,SpawnIndex}) -if Zone then -if RandomizeGroup then -return self:SpawnFromVec2(Zone:GetRandomVec2(),MinHeight,MaxHeight,SpawnIndex) -else -return self:SpawnFromVec2(Zone:GetVec2(),MinHeight,MaxHeight,SpawnIndex) -end -end -return nil -end -function SPAWN:InitUnControlled(UnControlled) -self:F2({self.SpawnTemplatePrefix,UnControlled}) -self.SpawnUnControlled=(UnControlled==true)and true or nil -for SpawnGroupID=1,self.SpawnMaxGroups do -self.SpawnGroups[SpawnGroupID].UnControlled=self.SpawnUnControlled -end -return self -end -function SPAWN:GetCoordinate() -local LateGroup=GROUP:FindByName(self.SpawnTemplatePrefix) -if LateGroup then -return LateGroup:GetCoordinate() -end -return nil -end -function SPAWN:SpawnGroupName(SpawnIndex) -self:F({self.SpawnTemplatePrefix,SpawnIndex}) -local SpawnPrefix=self.SpawnTemplatePrefix -if self.SpawnAliasPrefix then -SpawnPrefix=self.SpawnAliasPrefix -end -if SpawnIndex then -local SpawnName=string.format('%s#%03d',SpawnPrefix,SpawnIndex) -self:T(SpawnName) -return SpawnName -else -self:T(SpawnPrefix) -return SpawnPrefix -end -end -function SPAWN:GetFirstAliveGroup() -self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix}) -for SpawnIndex=1,self.SpawnCount do -local SpawnGroup=self:GetGroupFromIndex(SpawnIndex) -if SpawnGroup and SpawnGroup:IsAlive()then -return SpawnGroup,SpawnIndex -end -end -return nil,nil -end -function SPAWN:GetNextAliveGroup(SpawnIndexStart) -self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnIndexStart}) -SpawnIndexStart=SpawnIndexStart+1 -for SpawnIndex=SpawnIndexStart,self.SpawnCount do -local SpawnGroup=self:GetGroupFromIndex(SpawnIndex) -if SpawnGroup and SpawnGroup:IsAlive()then -return SpawnGroup,SpawnIndex -end -end -return nil,nil -end -function SPAWN:GetLastAliveGroup() -self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix}) -for SpawnIndex=self.SpawnCount,1,-1 do -local SpawnGroup=self:GetGroupFromIndex(SpawnIndex) -if SpawnGroup and SpawnGroup:IsAlive()then -self.SpawnIndex=SpawnIndex -return SpawnGroup -end -end -self.SpawnIndex=nil -return nil -end -function SPAWN:GetGroupFromIndex(SpawnIndex) -self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnIndex}) -if not SpawnIndex then -SpawnIndex=1 -end -if self.SpawnGroups and self.SpawnGroups[SpawnIndex]then -local SpawnGroup=self.SpawnGroups[SpawnIndex].Group -return SpawnGroup -else -return nil -end -end -function SPAWN:_GetPrefixFromGroup(SpawnGroup) -local GroupName=SpawnGroup:GetName() -if GroupName then -local SpawnPrefix=self:_GetPrefixFromGroupName(GroupName) -return SpawnPrefix -end -return nil -end -function SPAWN:_GetPrefixFromGroupName(SpawnGroupName) -if SpawnGroupName then -local SpawnPrefix=string.match(SpawnGroupName,".*#") -if SpawnPrefix then -SpawnPrefix=SpawnPrefix:sub(1,-2) -end -return SpawnPrefix -end -return nil -end -function SPAWN:GetSpawnIndexFromGroup(SpawnGroup) -self:F2({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnGroup}) -local IndexString=string.match(SpawnGroup:GetName(),"#(%d*)$"):sub(2) -local Index=tonumber(IndexString) -self:T3(IndexString,Index) -return Index -end -function SPAWN:_GetLastIndex() -self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix}) -return self.SpawnMaxGroups -end -function SPAWN:_InitializeSpawnGroups(SpawnIndex) -self:F3({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnIndex}) -if not self.SpawnGroups[SpawnIndex]then -self.SpawnGroups[SpawnIndex]={} -self.SpawnGroups[SpawnIndex].Visible=false -self.SpawnGroups[SpawnIndex].Spawned=false -self.SpawnGroups[SpawnIndex].UnControlled=false -self.SpawnGroups[SpawnIndex].SpawnTime=0 -self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix=self.SpawnTemplatePrefix -self.SpawnGroups[SpawnIndex].SpawnTemplate=self:_Prepare(self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix,SpawnIndex) -end -self:_RandomizeTemplate(SpawnIndex) -self:_RandomizeRoute(SpawnIndex) -return self.SpawnGroups[SpawnIndex] -end -function SPAWN:_GetGroupCategoryID(SpawnPrefix) -local TemplateGroup=Group.getByName(SpawnPrefix) -if TemplateGroup then -return TemplateGroup:getCategory() -else -return nil -end -end -function SPAWN:_GetGroupCoalitionID(SpawnPrefix) -local TemplateGroup=Group.getByName(SpawnPrefix) -if TemplateGroup then -return TemplateGroup:getCoalition() -else -return nil -end -end -function SPAWN:_GetGroupCountryID(SpawnPrefix) -self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnPrefix}) -local TemplateGroup=Group.getByName(SpawnPrefix) -if TemplateGroup then -local TemplateUnits=TemplateGroup:getUnits() -return TemplateUnits[1]:getCountry() -else -return nil -end -end -function SPAWN:_GetTemplate(SpawnTemplatePrefix) -self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnTemplatePrefix}) -local SpawnTemplate=nil -if _DATABASE.Templates.Groups[SpawnTemplatePrefix]==nil then -error('No Template exists for SpawnTemplatePrefix = '..SpawnTemplatePrefix) -end -local Template=_DATABASE.Templates.Groups[SpawnTemplatePrefix].Template -self:F({Template=Template}) -SpawnTemplate=UTILS.DeepCopy(_DATABASE.Templates.Groups[SpawnTemplatePrefix].Template) -if SpawnTemplate==nil then -error('No Template returned for SpawnTemplatePrefix = '..SpawnTemplatePrefix) -end -self:T3({SpawnTemplate}) -return SpawnTemplate -end -function SPAWN:_Prepare(SpawnTemplatePrefix,SpawnIndex) -self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix}) -local SpawnTemplate -if self.TweakedTemplate~=nil and self.TweakedTemplate==true then -BASE:I("WARNING: You are using a tweaked template.") -SpawnTemplate=self.SpawnTemplate -if self.MooseNameing==true then -SpawnTemplate.name=self:SpawnGroupName(SpawnIndex) -else -SpawnTemplate.name=self:SpawnGroupName() -end -else -SpawnTemplate=self:_GetTemplate(SpawnTemplatePrefix) -SpawnTemplate.name=self:SpawnGroupName(SpawnIndex) -end -SpawnTemplate.groupId=nil -SpawnTemplate.lateActivation=self.LateActivated or false -if SpawnTemplate.CategoryID==Group.Category.GROUND then -self:T3("For ground units, visible needs to be false...") -SpawnTemplate.visible=false -end -if self.SpawnGrouping then -local UnitAmount=#SpawnTemplate.units -self:F({UnitAmount=UnitAmount,SpawnGrouping=self.SpawnGrouping}) -if UnitAmount>self.SpawnGrouping then -for UnitID=self.SpawnGrouping+1,UnitAmount do -SpawnTemplate.units[UnitID]=nil -end -else -if UnitAmount0 then -self.Tstop=timer.getTime()+Delay -else -if self.tid then -self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!",self.ncalls)) -local status=pcall( -function() -timer.removeFunction(self.tid) -end -) -if status then -self:T2(self.lid..string.format("Stopped timer!")) -else -self:E(self.lid..string.format("WARNING: Could not remove timer function! isrunning=%s",tostring(self.isrunning))) -end -self.isrunning=false -end -end -return self -end -function TIMER:SetMaxFunctionCalls(Nmax) -self.ncallsMax=Nmax -return self -end -function TIMER:SetTimeInterval(dT) -self.dT=dT -return self -end -function TIMER:IsRunning() -return self.isrunning -end -function TIMER:_Function(time) -self.func(unpack(self.para)) -self.ncalls=self.ncalls+1 -local Tnext=self.dT and time+self.dT or nil -local stopme=false -if Tnext==nil then -self:T(self.lid..string.format("No next time as dT=nil ==> Stopping timer after %d function calls",self.ncalls)) -stopme=true -elseif self.Tstop and Tnext>self.Tstop then -self:T(self.lid..string.format("Stop time passed %.3f > %.3f ==> Stopping timer after %d function calls",Tnext,self.Tstop,self.ncalls)) -stopme=true -elseif self.ncallsMax and self.ncalls>=self.ncallsMax then -self:T(self.lid..string.format("Max function calls Nmax=%d reached ==> Stopping timer after %d function calls",self.ncallsMax,self.ncalls)) -stopme=true -end -if stopme then -self:Stop() -return nil -else -return Tnext -end -end -do -GOAL={ -ClassName="GOAL", -} -GOAL.Players={} -GOAL.TotalContributions=0 -function GOAL:New() -local self=BASE:Inherit(self,FSM:New()) -self:F({}) -self:SetStartState("Pending") -self:AddTransition("*","Achieved","Achieved") -self:SetEventPriority(5) -return self -end -function GOAL:AddPlayerContribution(PlayerName) -self:F({PlayerName}) -self.Players[PlayerName]=self.Players[PlayerName]or 0 -self.Players[PlayerName]=self.Players[PlayerName]+1 -self.TotalContributions=self.TotalContributions+1 -end -function GOAL:GetPlayerContribution(PlayerName) -return self.Players[PlayerName]or 0 -end -function GOAL:GetPlayerContributions() -return self.Players or{} -end -function GOAL:GetTotalContributions() -return self.TotalContributions or 0 -end -function GOAL:IsAchieved() -return self:Is("Achieved") -end -end -do -SPOT={ -ClassName="SPOT", -} -function SPOT:New(Recce) -local self=BASE:Inherit(self,FSM:New()) -self:F({}) -self:SetStartState("Off") -self:AddTransition("Off","LaseOn","On") -self:AddTransition("Off","LaseOnCoordinate","On") -self:AddTransition("On","Lasing","On") -self:AddTransition({"On","Destroyed"},"LaseOff","Off") -self:AddTransition("*","Destroyed","Destroyed") -self.Recce=Recce -self.RecceName=self.Recce:GetName() -self.LaseScheduler=SCHEDULER:New(self) -self:SetEventPriority(5) -self.Lasing=false -return self -end -function SPOT:onafterLaseOn(From,Event,To,Target,LaserCode,Duration) -self:T({From,Event,To}) -self:T2({"LaseOn",Target,LaserCode,Duration}) -local function StopLase(self) -self:LaseOff() -end -self.Target=Target -self.TargetName=Target:GetName() -self.LaserCode=LaserCode -self.Lasing=true -local RecceDcsUnit=self.Recce:GetDCSObject() -local relativespot=self.relstartpos or{x=0,y=2,z=0} -self.SpotIR=Spot.createInfraRed(RecceDcsUnit,relativespot,Target:GetPointVec3():AddY(1):GetVec3()) -self.SpotLaser=Spot.createLaser(RecceDcsUnit,relativespot,Target:GetPointVec3():AddY(1):GetVec3(),LaserCode) -if Duration then -self.ScheduleID=self.LaseScheduler:Schedule(self,StopLase,{self},Duration) -end -self:HandleEvent(EVENTS.Dead) -self:__Lasing(-1) -return self -end -function SPOT:onafterLaseOnCoordinate(From,Event,To,Coordinate,LaserCode,Duration) -self:T2({"LaseOnCoordinate",Coordinate,LaserCode,Duration}) -local function StopLase(self) -self:LaseOff() -end -self.Target=nil -self.TargetCoord=Coordinate -self.LaserCode=LaserCode -self.Lasing=true -local RecceDcsUnit=self.Recce:GetDCSObject() -self.SpotIR=Spot.createInfraRed(RecceDcsUnit,{x=0,y=1,z=0},Coordinate:GetVec3()) -self.SpotLaser=Spot.createLaser(RecceDcsUnit,{x=0,y=1,z=0},Coordinate:GetVec3(),LaserCode) -if Duration then -self.ScheduleID=self.LaseScheduler:Schedule(self,StopLase,{self},Duration) -end -self:__Lasing(-1) -return self -end -function SPOT:OnEventDead(EventData) -self:T2({Dead=EventData.IniDCSUnitName,Target=self.Target}) -if self.Target then -if EventData.IniDCSUnitName==self.TargetName then -self:F({"Target dead ",self.TargetName}) -self:Destroyed() -self:LaseOff() -end -end -if self.Recce then -if EventData.IniDCSUnitName==self.RecceName then -self:F({"Recce dead ",self.RecceName}) -self:LaseOff() -end -end -return self -end -function SPOT:onafterLasing(From,Event,To) -self:T({From,Event,To}) -if self.Lasing then -if self.Target and self.Target:IsAlive()then -self.SpotIR:setPoint(self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/200):AddX(math.random(-100,100)/200):GetVec3()) -self.SpotLaser:setPoint(self.Target:GetPointVec3():AddY(1):GetVec3()) -self:__Lasing(0.2) -elseif self.TargetCoord then -local irvec3={x=self.TargetCoord.x+math.random(-100,100)/200,y=self.TargetCoord.y+math.random(-100,100)/200,z=self.TargetCoord.z} -local lsvec3={x=self.TargetCoord.x,y=self.TargetCoord.y,z=self.TargetCoord.z} -self.SpotIR:setPoint(irvec3) -self.SpotLaser:setPoint(lsvec3) -self:__Lasing(0.2) -else -self:F({"Target is not alive",self.Target:IsAlive()}) -end -end -return self -end -function SPOT:onafterLaseOff(From,Event,To) -self:T({From,Event,To}) -self:T2({"Stopped lasing for ",self.Target and self.Target:GetName()or"coord",SpotIR=self.SportIR,SpotLaser=self.SpotLaser}) -self.Lasing=false -self.SpotIR:destroy() -self.SpotLaser:destroy() -self.SpotIR=nil -self.SpotLaser=nil -if self.ScheduleID then -self.LaseScheduler:Stop(self.ScheduleID) -end -self.ScheduleID=nil -self.Target=nil -return self -end -function SPOT:IsLasing() -return self.Lasing -end -function SPOT:SetRelativeStartPosition(position) -self.relstartpos=position or{x=0,y=2,z=0} -return self -end -end -MARKEROPS_BASE={ -ClassName="MARKEROPS", -Tag="mytag", -Keywords={}, -version="0.1.1", -debug=false, -Casesensitive=true, -} -function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) -local self=BASE:Inherit(self,FSM:New()) -self.lid=string.format("MARKEROPS_BASE %s | ",tostring(self.version)) -self.Tag=Tagname or"mytag" -self.Keywords=Keywords or{} -self.debug=false -self.Casesensitive=true -if Casesensitive and Casesensitive==false then -self.Casesensitive=false -end -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","MarkAdded","*") -self:AddTransition("*","MarkChanged","*") -self:AddTransition("*","MarkDeleted","*") -self:AddTransition("Running","Stop","Stopped") -self:HandleEvent(EVENTS.MarkAdded,self.OnEventMark) -self:HandleEvent(EVENTS.MarkChange,self.OnEventMark) -self:HandleEvent(EVENTS.MarkRemoved,self.OnEventMark) -self:I(self.lid..string.format("started for %s",self.Tag)) -self:__Start(1) -return self -end -function MARKEROPS_BASE:OnEventMark(Event) -self:T({Event}) -if Event==nil or Event.idx==nil then -self:E("Skipping onEvent. Event or Event.idx unknown.") -return true -end -local vec3={y=Event.pos.y,x=Event.pos.x,z=Event.pos.z} -local coord=COORDINATE:NewFromVec3(vec3) -if self.debug then -local coordtext=coord:ToStringLLDDM() -local text=tostring(Event.text) -local m=MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll() -end -if Event.id==world.event.S_EVENT_MARK_ADDED then -self:T({event="S_EVENT_MARK_ADDED",carrier=self.groupname,vec3=Event.pos}) -local Eventtext=tostring(Event.text) -if Eventtext~=nil then -if self:_MatchTag(Eventtext)then -local matchtable=self:_MatchKeywords(Eventtext) -self:MarkAdded(Eventtext,matchtable,coord) -end -end -elseif Event.id==world.event.S_EVENT_MARK_CHANGE then -self:T({event="S_EVENT_MARK_CHANGE",carrier=self.groupname,vec3=Event.pos}) -local Eventtext=tostring(Event.text) -if Eventtext~=nil then -if self:_MatchTag(Eventtext)then -local matchtable=self:_MatchKeywords(Eventtext) -self:MarkChanged(Eventtext,matchtable,coord,Event.idx) -end -end -elseif Event.id==world.event.S_EVENT_MARK_REMOVED then -self:T({event="S_EVENT_MARK_REMOVED",carrier=self.groupname,vec3=Event.pos}) -local Eventtext=tostring(Event.text) -if Eventtext~=nil then -if self:_MatchTag(Eventtext)then -self:MarkDeleted() -end -end -end -end -function MARKEROPS_BASE:_MatchTag(Eventtext) -local matches=false -if not self.Casesensitive then -local type=string.lower(self.Tag) -if string.find(string.lower(Eventtext),type)then -matches=true -end -else -local type=self.Tag -if string.find(Eventtext,type)then -matches=true -end -end -return matches -end -function MARKEROPS_BASE:_MatchKeywords(Eventtext) -local matchtable={} -local keytable=self.Keywords -for _index,_word in pairs(keytable)do -if string.find(string.lower(Eventtext),string.lower(_word))then -table.insert(matchtable,_word) -end -end -return matchtable -end -function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord) -self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) -end -function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord) -self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) -end -function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To) -self:T({self.lid,From,Event,To}) -end -function MARKEROPS_BASE:onenterStopped(From,Event,To) -self:T({self.lid,From,Event,To}) -self:UnHandleEvent(EVENTS.MarkAdded) -self:UnHandleEvent(EVENTS.MarkChange) -self:UnHandleEvent(EVENTS.MarkRemoved) -end -TEXTANDSOUND={ -ClassName="TEXTANDSOUND", -version="0.0.1", -lid="", -locale="en", -entries={}, -textclass="", -} -function TEXTANDSOUND:New(ClassName,Defaultlocale) -local self=BASE:Inherit(self,BASE:New()) -self.lid=string.format("%s (%s) | ",self.ClassName,self.version) -self.locale=Defaultlocale or(_SETTINGS:GetLocale()or"en") -self.textclass=ClassName or"none" -self.entries={} -local initentry={} -initentry.Classname=ClassName -initentry.Data={} -initentry.Locale=self.locale -self.entries[self.locale]=initentry -self:I(self.lid.."Instantiated.") -self:T({self.entries[self.locale]}) -return self -end -function TEXTANDSOUND:AddEntry(Locale,ID,Text,Soundfile,Soundlength,Subtitle) -self:T(self.lid.."AddEntry") -local locale=Locale or self.locale -local dataentry={} -dataentry.ID=ID or"1" -dataentry.Text=Text or"none" -dataentry.Soundfile=Soundfile -dataentry.Soundlength=Soundlength or 0 -dataentry.Subtitle=Subtitle -if not self.entries[locale]then -local initentry={} -initentry.Classname=self.textclass -initentry.Data={} -initentry.Locale=locale -self.entries[locale]=initentry -end -self.entries[locale].Data[ID]=dataentry -self:T({self.entries[locale].Data}) -return self -end -function TEXTANDSOUND:GetEntry(ID,Locale) -self:T(self.lid.."GetEntry") -local locale=Locale or self.locale -if not self.entries[locale]then -locale=self.locale -end -local Text,Soundfile,Soundlength,Subtitle=nil,nil,0,nil -if self.entries[locale]then -if self.entries[locale].Data then -local data=self.entries[locale].Data[ID] -if data then -Text=data.Text -Soundfile=data.Soundfile -Soundlength=data.Soundlength -Subtitle=data.Subtitle -elseif self.entries[self.locale].Data[ID]then -local data=self.entries[self.locale].Data[ID] -Text=data.Text -Soundfile=data.Soundfile -Soundlength=data.Soundlength -Subtitle=data.Subtitle -end -end -else -return nil,nil,0,nil -end -return Text,Soundfile,Soundlength,Subtitle -end -function TEXTANDSOUND:GetDefaultLocale() -self:T(self.lid.."GetDefaultLocale") -return self.locale -end -function TEXTANDSOUND:SetDefaultLocale(locale) -self:T(self.lid.."SetDefaultLocale") -self.locale=locale or"en" -return self -end -function TEXTANDSOUND:HasLocale(Locale) -self:T(self.lid.."HasLocale") -return self.entries[Locale]and true or false -end -function TEXTANDSOUND:FlushToLog() -self:I(self.lid.."Flushing entries:") -local text=string.format("Textclass: %s | Default Locale: %s",self.textclass,self.locale) -for _,_entry in pairs(self.entries)do -local entry=_entry -local text=string.format("Textclassname: %s | Locale: %s",entry.Classname,entry.Locale) -self:I(text) -for _ID,_data in pairs(entry.Data)do -local data=_data -local text=string.format("ID: %s\nText: %s\nSoundfile: %s With length: %d\nSubtitle: %s",tostring(_ID),data.Text or"none",data.Soundfile or"none",data.Soundlength or 0,data.Subtitle or"none") -self:I(text) -end -end -return self -end -PATHLINE={ -ClassName="PATHLINE", -lid=nil, -points={}, -} -PATHLINE.version="0.1.1" -function PATHLINE:New(Name) -local self=BASE:Inherit(self,BASE:New()) -self.name=Name or"Unknown Path" -self.lid=string.format("PATHLINE %s | ",Name) -return self -end -function PATHLINE:NewFromVec2Array(Name,Vec2Array) -local self=PATHLINE:New(Name) -for i=1,#Vec2Array do -self:AddPointFromVec2(Vec2Array[i]) -end -return self -end -function PATHLINE:NewFromVec3Array(Name,Vec3Array) -local self=PATHLINE:New(Name) -for i=1,#Vec3Array do -self:AddPointFromVec3(Vec3Array[i]) -end -return self -end -function PATHLINE:FindByName(Name) -local pathline=_DATABASE:FindPathline(Name) -return pathline -end -function PATHLINE:AddPointFromVec2(Vec2) -if Vec2 then -local point=self:_CreatePoint(Vec2) -table.insert(self.points,point) -end -return self -end -function PATHLINE:AddPointFromVec3(Vec3) -if Vec3 then -local point=self:_CreatePoint(Vec3) -table.insert(self.points,point) -end -return self -end -function PATHLINE:GetName() -return self.name -end -function PATHLINE:GetNumberOfPoints() -local N=#self.points -return N -end -function PATHLINE:GetPoints() -return self.points -end -function PATHLINE:GetPoints3D() -local vecs={} -for _,_point in pairs(self.points)do -local point=_point -table.insert(vecs,point.vec3) -end -return vecs -end -function PATHLINE:GetPoints2D() -local vecs={} -for _,_point in pairs(self.points)do -local point=_point -table.insert(vecs,point.vec2) -end -return vecs -end -function PATHLINE:GetCoordinates() -local vecs={} -for _,_point in pairs(self.points)do -local point=_point -local coord=COORDINATE:NewFromVec3(point.vec3) -table.insert(vecs,coord) -end -return vecs -end -function PATHLINE:GetPointFromIndex(n) -local N=self:GetNumberOfPoints() -n=n or 1 -local point=nil -if n>=1 and n<=N then -point=self.points[n] -else -self:E(self.lid..string.format("ERROR: No point in pathline for N=%s",tostring(n))) -end -return point -end -function PATHLINE:GetPoint3DFromIndex(n) -local point=self:GetPointFromIndex(n) -if point then -return point.vec3 -end -return nil -end -function PATHLINE:GetPoint2DFromIndex(n) -local point=self:GetPointFromIndex(n) -if point then -return point.vec2 -end -return nil -end -function PATHLINE:MarkPoints(Switch) -for i,_point in pairs(self.points)do -local point=_point -if Switch==false then -if point.markerID then -UTILS.RemoveMark(point.markerID,Delay) -end -else -if point.markerID then -UTILS.RemoveMark(point.markerID) -end -point.markerID=UTILS.GetMarkID() -local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m\nDepth=%.1f m",self.name,i,point.surfaceType,point.landHeight,point.depth) -trigger.action.markToAll(point.markerID,text,point.vec3,"") -end -end -end -function PATHLINE:_CreatePoint(Vec) -local point={} -if Vec.z then -point.vec3=UTILS.DeepCopy(Vec) -point.vec2={x=Vec.x,y=Vec.z} -else -point.vec2=UTILS.DeepCopy(Vec) -point.vec3={x=Vec.x,y=land.getHeight(Vec),z=Vec.y} -end -point.surfaceType=land.getSurfaceType(point.vec2) -point.landHeight,point.depth=land.getSurfaceHeightWithSeabed(point.vec2) -point.markerID=nil -return point -end -CLIENTMENU={ -ClassName="CLIENTMENUE", -lid="", -version="0.1.1", -name=nil, -path=nil, -group=nil, -client=nil, -GroupID=nil, -Children={}, -Once=false, -Generic=false, -debug=false, -Controller=nil, -groupname=nil, -} -CLIENTMENU_ID=0 -function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...) -local self=BASE:Inherit(self,BASE:New()) -CLIENTMENU_ID=CLIENTMENU_ID+1 -self.ID=CLIENTMENU_ID -if Client then -self.group=Client:GetGroup() -self.client=Client -self.GroupID=self.group:GetID() -self.groupname=self.group:GetName()or"Unknown Groupname" -else -self.Generic=true -end -self.name=Text or"unknown entry" -if Parent then -if Parent:IsInstanceOf("MENU_BASE")then -self.parentpath=Parent.MenuPath -else -self.parentpath=Parent:GetPath() -Parent:AddChild(self) -end -end -self.Parent=Parent -self.Function=Function -self.Functionargs=arg or{} -table.insert(self.Functionargs,self.group) -table.insert(self.Functionargs,self.client) -if self.Functionargs and self.debug then -self:T({"Functionargs",self.Functionargs}) -end -if not self.Generic then -if Function~=nil then -local ErrorHandler=function(errmsg) -env.info("MOOSE Error in CLIENTMENU COMMAND function: "..errmsg) -if BASE.Debug~=nil then -env.info(BASE.Debug.traceback()) -end -return errmsg -end -self.CallHandler=function() -local function MenuFunction() -return self.Function(unpack(self.Functionargs)) -end -local Status,Result=xpcall(MenuFunction,ErrorHandler) -if self.Once==true then -self:Clear() -end -end -self.path=missionCommands.addCommandForGroup(self.GroupID,Text,self.parentpath,self.CallHandler) -else -self.path=missionCommands.addSubMenuForGroup(self.GroupID,Text,self.parentpath) -end -else -if self.parentpath then -self.path=UTILS.DeepCopy(self.parentpath) -else -self.path={} -end -self.path[#self.path+1]=Text -end -self.UUID=table.concat(self.path,";") -self:T({self.UUID}) -self.Once=false -self.lid=string.format("CLIENTMENU %s | %s | ",self.ID,self.name) -self:T(self.lid.."Created") -return self -end -function CLIENTMENU:CreateUUID(Parent,Text) -local path={} -if Parent and Parent.path then -path=Parent.path -end -path[#path+1]=Text -local UUID=table.concat(path,";") -return UUID -end -function CLIENTMENU:SetController(Controller) -self.Controller=Controller -return self -end -function CLIENTMENU:SetOnce() -self:T(self.lid.."SetOnce") -self.Once=true -return self -end -function CLIENTMENU:RemoveF10() -self:T(self.lid.."RemoveF10") -if self.GroupID then -local function RemoveFunction() -return missionCommands.removeItemForGroup(self.GroupID,self.path) -end -local status,err=pcall(RemoveFunction) -if not status then -self:I(string.format("**** Error Removing Menu Entry %s for %s!",tostring(self.name),self.groupname)) -end -end -return self -end -function CLIENTMENU:GetPath() -self:T(self.lid.."GetPath") -return self.path -end -function CLIENTMENU:GetUUID() -self:T(self.lid.."GetUUID") -return self.UUID -end -function CLIENTMENU:AddChild(Child) -self:T(self.lid.."AddChild "..Child.ID) -table.insert(self.Children,Child.ID,Child) -return self -end -function CLIENTMENU:RemoveChild(Child) -self:T(self.lid.."RemoveChild "..Child.ID) -table.remove(self.Children,Child.ID) -return self -end -function CLIENTMENU:RemoveSubEntries() -self:T(self.lid.."RemoveSubEntries") -self:T({self.Children}) -for _id,_entry in pairs(self.Children)do -self:T("Removing ".._id) -if _entry then -_entry:RemoveSubEntries() -_entry:RemoveF10() -if _entry.Parent then -_entry.Parent:RemoveChild(self) -end -end -end -return self -end -function CLIENTMENU:Clear() -self:T(self.lid.."Clear") -for _id,_entry in pairs(self.Children)do -if _entry then -_entry:RemoveSubEntries() -_entry=nil -end -end -self:RemoveF10() -if self.Parent then -self.Parent:RemoveChild(self) -end -return self -end -CLIENTMENUMANAGER={ -ClassName="CLIENTMENUMANAGER", -lid="", -version="0.1.4", -name=nil, -clientset=nil, -menutree={}, -flattree={}, -playertree={}, -entrycount=0, -rootentries={}, -debug=true, -PlayerMenu={}, -Coalition=nil, -} -function CLIENTMENUMANAGER:New(ClientSet,Alias,Coalition) -local self=BASE:Inherit(self,BASE:New()) -self.clientset=ClientSet -self.PlayerMenu={} -self.name=Alias or"Nightshift" -self.Coalition=Coalition or coalition.side.BLUE -self.lid=string.format("CLIENTMENUMANAGER %s | %s | ",self.version,self.name) -if self.debug then -self:I(self.lid.."Created") -end -return self -end -function CLIENTMENUMANAGER:_EventHandler(EventData) -self:T(self.lid.."_EventHandler: "..EventData.id) -if EventData.id==EVENTS.PlayerLeaveUnit or EventData.id==EVENTS.Ejection or EventData.id==EVENTS.Crash or EventData.id==EVENTS.PilotDead then -self:T(self.lid.."Leave event for player: "..tostring(EventData.IniPlayerName)) -local Client=_DATABASE:FindClient(EventData.IniUnitName) -if Client then -self:ResetMenu(Client) -end -elseif(EventData.id==EVENTS.PlayerEnterAircraft)and EventData.IniCoalition==self.Coalition then -if EventData.IniPlayerName and EventData.IniGroup then -if(not self.clientset:IsIncludeObject(_DATABASE:FindClient(EventData.IniUnitName)))then -self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName) -return self -end -local player=_DATABASE:FindClient(EventData.IniUnitName) -self:Propagate(player) -end -elseif EventData.id==EVENTS.PlayerEnterUnit then -local grp=GROUP:FindByName(EventData.IniGroupName) -if grp:IsGround()then -self:T(string.format("Player %s entered GROUND unit %s!",EventData.IniPlayerName,EventData.IniUnitName)) -local IsPlayer=EventData.IniDCSUnit:getPlayerName() -if IsPlayer then -local client=_DATABASE.CLIENTS[EventData.IniDCSUnitName] -if not client then -self:I(string.format("Player '%s' joined ground unit '%s' of group '%s'",tostring(EventData.IniPlayerName),tostring(EventData.IniDCSUnitName),tostring(EventData.IniDCSGroupName))) -client=_DATABASE:AddClient(EventData.IniDCSUnitName) -client:AddPlayer(EventData.IniPlayerName) -if not _DATABASE.PLAYERS[EventData.IniPlayerName]then -_DATABASE:AddPlayer(EventData.IniUnitName,EventData.IniPlayerName) -end -local Settings=SETTINGS:Set(EventData.IniPlayerName) -Settings:SetPlayerMenu(EventData.IniUnit) -end -self:Propagate(client) -end -end -end -return self -end -function CLIENTMENUMANAGER:InitAutoPropagation() -self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler) -self:HandleEvent(EVENTS.Ejection,self._EventHandler) -self:HandleEvent(EVENTS.Crash,self._EventHandler) -self:HandleEvent(EVENTS.PilotDead,self._EventHandler) -self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) -self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler) -self:SetEventPriority(5) -return self -end -function CLIENTMENUMANAGER:NewEntry(Text,Parent,Function,...) -self:T(self.lid.."NewEntry "..Text or"None") -self.entrycount=self.entrycount+1 -local entry=CLIENTMENU:NewEntry(nil,Text,Parent,Function,unpack(arg)) -if not Parent then -self.rootentries[self.entrycount]=entry -end -local depth=#entry.path -if not self.menutree[depth]then self.menutree[depth]={}end -table.insert(self.menutree[depth],entry.UUID) -self.flattree[entry.UUID]=entry -return entry -end -function CLIENTMENUMANAGER:EntryUUIDExists(UUID) -local exists=self.flattree[UUID]and true or false -return exists -end -function CLIENTMENUMANAGER:FindEntryByUUID(UUID) -self:T(self.lid.."FindEntryByUUID "..UUID or"None") -local entry=nil -for _gid,_entry in pairs(self.flattree)do -local Entry=_entry -if Entry and Entry.UUID==UUID then -entry=Entry -end -end -return entry -end -function CLIENTMENUMANAGER:FindUUIDsByText(Text,Parent) -self:T(self.lid.."FindUUIDsByText "..Text or"None") -local matches={} -local entries={} -local n=0 -for _uuid,_entry in pairs(self.flattree)do -local Entry=_entry -if Parent then -if Entry and string.find(Entry.name,Text,1,true)and string.find(Entry.UUID,Parent.UUID,1,true)then -table.insert(matches,_uuid) -table.insert(entries,Entry) -n=n+1 -end -else -if Entry and string.find(Entry.name,Text,1,true)then -table.insert(matches,_uuid) -table.insert(entries,Entry) -n=n+1 -end -end -end -return matches,entries,n -end -function CLIENTMENUMANAGER:FindEntriesByText(Text,Parent) -self:T(self.lid.."FindEntriesByText "..Text or"None") -local matches,objects,number=self:FindUUIDsByText(Text,Parent) -return objects,number -end -function CLIENTMENUMANAGER:FindUUIDsByParent(Parent) -self:T(self.lid.."FindUUIDsByParent") -local matches={} -local entries={} -local n=0 -for _uuid,_entry in pairs(self.flattree)do -local Entry=_entry -if Parent then -if Entry and string.find(Entry.UUID,Parent.UUID,1,true)then -table.insert(matches,_uuid) -table.insert(entries,Entry) -n=n+1 -end -end -end -return matches,entries,n -end -function CLIENTMENUMANAGER:FindEntriesByParent(Parent) -self:T(self.lid.."FindEntriesByParent") -local matches,objects,number=self:FindUUIDsByParent(Parent) -return objects,number -end -function CLIENTMENUMANAGER:ChangeEntryText(Entry,Text,Client) -self:T(self.lid.."ChangeEntryText "..Text or"None") -local newentry=CLIENTMENU:NewEntry(nil,Text,Entry.Parent,Entry.Function,unpack(Entry.Functionargs)) -self:DeleteF10Entry(Entry,Client) -self:DeleteGenericEntry(Entry) -if not Entry.Parent then -self.rootentries[self.entrycount]=newentry -end -local depth=#newentry.path -if not self.menutree[depth]then self.menutree[depth]={}end -table.insert(self.menutree[depth],newentry.UUID) -self.flattree[newentry.UUID]=newentry -self:AddEntry(newentry,Client) -return self -end -function CLIENTMENUMANAGER:Propagate(Client) -self:T(self.lid.."Propagate") -local Set=self.clientset.Set -if Client then -Set={Client} -end -self:ResetMenu(Client) -for _,_client in pairs(Set)do -local client=_client -if client and client:IsAlive()then -local playername=client:GetPlayerName()or"none" -if not self.playertree[playername]then -self.playertree[playername]={} -end -for level,branch in pairs(self.menutree)do -self:T("Building branch:"..level) -for _,leaf in pairs(branch)do -self:T("Building leaf:"..leaf) -local entry=self:FindEntryByUUID(leaf) -if entry then -self:T("Found generic entry:"..entry.UUID) -local parent=nil -if entry.Parent and entry.Parent.UUID then -parent=self.playertree[playername][entry.Parent.UUID]or self:FindEntryByUUID(entry.Parent.UUID) -end -self.playertree[playername][entry.UUID]=CLIENTMENU:NewEntry(client,entry.name,parent,entry.Function,unpack(entry.Functionargs)) -self.playertree[playername][entry.UUID].Once=entry.Once -else -self:T("NO generic entry for:"..leaf) -end -end -end -end -end -return self -end -function CLIENTMENUMANAGER:AddEntry(Entry,Client) -self:T(self.lid.."AddEntry") -local Set=self.clientset.Set -if Client then -Set={Client} -end -for _,_client in pairs(Set)do -local client=_client -if client and client:IsAlive()then -local playername=client:GetPlayerName() -if Entry then -self:T("Adding generic entry:"..Entry.UUID) -local parent=nil -if not self.playertree[playername]then -self.playertree[playername]={} -end -if Entry.Parent and Entry.Parent.UUID then -parent=self.playertree[playername][Entry.Parent.UUID]or self:FindEntryByUUID(Entry.Parent.UUID) -end -self.playertree[playername][Entry.UUID]=CLIENTMENU:NewEntry(client,Entry.name,parent,Entry.Function,unpack(Entry.Functionargs)) -self.playertree[playername][Entry.UUID].Once=Entry.Once -else -self:T("NO generic entry given") -end -end -end -return self -end -function CLIENTMENUMANAGER:ResetMenu(Client) -self:T(self.lid.."ResetMenu") -for _,_entry in pairs(self.rootentries)do -if _entry then -self:DeleteF10Entry(_entry,Client) -end -end -return self -end -function CLIENTMENUMANAGER:ResetMenuComplete() -self:T(self.lid.."ResetMenuComplete") -for _,_entry in pairs(self.rootentries)do -if _entry then -self:DeleteF10Entry(_entry) -end -end -self.playertree=nil -self.playertree={} -self.rootentries=nil -self.rootentries={} -self.menutree=nil -self.menutree={} -return self -end -function CLIENTMENUMANAGER:DeleteF10Entry(Entry,Client) -self:T(self.lid.."DeleteF10Entry") -local Set=self.clientset.Set -if Client then -Set={Client} -end -for _,_client in pairs(Set)do -if _client and _client:IsAlive()then -local playername=_client:GetPlayerName() -if self.playertree[playername]then -local centry=self.playertree[playername][Entry.UUID] -if centry then -centry:Clear() -end -end -end -end -return self -end -function CLIENTMENUMANAGER:DeleteGenericEntry(Entry) -self:T(self.lid.."DeleteGenericEntry") -if Entry.Children and#Entry.Children>0 then -self:RemoveGenericSubEntries(Entry) -end -local depth=#Entry.path -local uuid=Entry.UUID -local tbl=UTILS.DeepCopy(self.menutree) -if tbl[depth]then -for i=depth,#tbl do -for _id,_uuid in pairs(tbl[i])do -self:T(_uuid) -if string.find(_uuid,uuid,1,true)or _uuid==uuid then -self.menutree[i][_id]=nil -self.flattree[_uuid]=nil -end -end -end -end -return self -end -function CLIENTMENUMANAGER:RemoveGenericSubEntries(Entry) -self:T(self.lid.."RemoveGenericSubEntries") -local depth=#Entry.path+1 -local uuid=Entry.UUID -local tbl=UTILS.DeepCopy(self.menutree) -if tbl[depth]then -for i=depth,#tbl do -self:T("Level = "..i) -for _id,_uuid in pairs(tbl[i])do -self:T(_uuid) -if string.find(_uuid,uuid,1,true)then -self:T("Match for ".._uuid) -self.menutree[i][_id]=nil -self.flattree[_uuid]=nil -end -end -end -end -return self -end -function CLIENTMENUMANAGER:RemoveF10SubEntries(Entry,Client) -self:T(self.lid.."RemoveSubEntries") -local Set=self.clientset.Set -if Client then -Set={Client} -end -for _,_client in pairs(Set)do -if _client and _client:IsAlive()then -local playername=_client:GetPlayerName() -if self.playertree[playername]then -local centry=self.playertree[playername][Entry.UUID] -centry:RemoveSubEntries() -end -end -end -return self -end -OBJECT={ -ClassName="OBJECT", -ObjectName="", -} -function OBJECT:New(ObjectName) -local self=BASE:Inherit(self,BASE:New()) -self:F2(ObjectName) -self.ObjectName=ObjectName -return self -end -function OBJECT:GetID() -local DCSObject=self:GetDCSObject() -if DCSObject then -local ObjectID=DCSObject:getID() -return ObjectID -end -self:E({"Cannot GetID",Name=self.ObjectName,Class=self:GetClassName()}) -return nil -end -function OBJECT:Destroy() -local DCSObject=self:GetDCSObject() -if DCSObject then -DCSObject:destroy(false) -return true -end -self:E({"Cannot Destroy",Name=self.ObjectName,Class=self:GetClassName()}) -return nil -end -IDENTIFIABLE={ -ClassName="IDENTIFIABLE", -IdentifiableName="", -} -local _CategoryName={ -[Unit.Category.AIRPLANE]="Airplane", -[Unit.Category.HELICOPTER]="Helicopter", -[Unit.Category.GROUND_UNIT]="Ground Identifiable", -[Unit.Category.SHIP]="Ship", -[Unit.Category.STRUCTURE]="Structure", -} -function IDENTIFIABLE:New(IdentifiableName) -local self=BASE:Inherit(self,OBJECT:New(IdentifiableName)) -self:F2(IdentifiableName) -self.IdentifiableName=IdentifiableName -return self -end -function IDENTIFIABLE:IsAlive() -self:F3(self.IdentifiableName) -local DCSIdentifiable=self:GetDCSObject() -if DCSIdentifiable then -local IdentifiableIsAlive=DCSIdentifiable:isExist() -return IdentifiableIsAlive -end -return false -end -function IDENTIFIABLE:GetName() -local IdentifiableName=self.IdentifiableName -return IdentifiableName -end -function IDENTIFIABLE:GetTypeName() -self:F2(self.IdentifiableName) -local DCSIdentifiable=self:GetDCSObject() -if DCSIdentifiable then -local IdentifiableTypeName=DCSIdentifiable:getTypeName() -self:T3(IdentifiableTypeName) -return IdentifiableTypeName -end -self:F(self.ClassName.." "..self.IdentifiableName.." not found!") -return nil -end -function IDENTIFIABLE:GetCategory() -self:F2(self.ObjectName) -local DCSObject=self:GetDCSObject() -if DCSObject then -local ObjectCategory=DCSObject:getCategory() -self:T3(ObjectCategory) -return ObjectCategory -end -return nil -end -function IDENTIFIABLE:GetCategoryName() -local DCSIdentifiable=self:GetDCSObject() -if DCSIdentifiable then -local IdentifiableCategoryName=_CategoryName[self:GetDesc().category] -return IdentifiableCategoryName -end -self:F(self.ClassName.." "..self.IdentifiableName.." not found!") -return nil -end -function IDENTIFIABLE:GetCoalition() -self:F2(self.IdentifiableName) -local DCSIdentifiable=self:GetDCSObject() -if DCSIdentifiable then -local IdentifiableCoalition=DCSIdentifiable:getCoalition() -self:T3(IdentifiableCoalition) -return IdentifiableCoalition -end -self:F(self.ClassName.." "..self.IdentifiableName.." not found!") -return nil -end -function IDENTIFIABLE:GetCoalitionName() -self:F2(self.IdentifiableName) -local DCSIdentifiable=self:GetDCSObject() -if DCSIdentifiable then -local IdentifiableCoalition=DCSIdentifiable:getCoalition() -self:T3(IdentifiableCoalition) -return UTILS.GetCoalitionName(IdentifiableCoalition) -end -self:F(self.ClassName.." "..self.IdentifiableName.." not found!") -return nil -end -function IDENTIFIABLE:GetCountry() -self:F2(self.IdentifiableName) -local DCSIdentifiable=self:GetDCSObject() -if DCSIdentifiable then -local IdentifiableCountry=DCSIdentifiable:getCountry() -self:T3(IdentifiableCountry) -return IdentifiableCountry -end -self:F(self.ClassName.." "..self.IdentifiableName.." not found!") -return nil -end -function IDENTIFIABLE:GetCountryName() -self:F2(self.IdentifiableName) -local countryid=self:GetCountry() -for name,id in pairs(country.id)do -if countryid==id then -return name -end -end -end -function IDENTIFIABLE:GetDesc() -self:F2(self.IdentifiableName) -local DCSIdentifiable=self:GetDCSObject() -if DCSIdentifiable then -local IdentifiableDesc=DCSIdentifiable:getDesc() -self:T2(IdentifiableDesc) -return IdentifiableDesc -end -self:F(self.ClassName.." "..self.IdentifiableName.." not found!") -return nil -end -function IDENTIFIABLE:HasAttribute(AttributeName) -self:F2(self.IdentifiableName) -local DCSIdentifiable=self:GetDCSObject() -if DCSIdentifiable then -local IdentifiableHasAttribute=DCSIdentifiable:hasAttribute(AttributeName) -self:T2(IdentifiableHasAttribute) -return IdentifiableHasAttribute -end -self:F(self.ClassName.." "..self.IdentifiableName.." not found!") -return nil -end -function IDENTIFIABLE:GetCallsign() -return'' -end -function IDENTIFIABLE:GetThreatLevel() -return 0,"Scenery" -end -POSITIONABLE={ -ClassName="POSITIONABLE", -PositionableName="", -coordinate=nil, -pointvec3=nil, -} -POSITIONABLE.__={} -POSITIONABLE.__.Cargo={} -function POSITIONABLE:New(PositionableName) -local self=BASE:Inherit(self,IDENTIFIABLE:New(PositionableName)) -self.PositionableName=PositionableName -return self -end -function POSITIONABLE:Destroy(GenerateEvent) -self:F2(self.ObjectName) -local DCSObject=self:GetDCSObject() -if DCSObject then -local UnitGroup=self:GetGroup() -local UnitGroupName=UnitGroup:GetName() -self:F({UnitGroupName=UnitGroupName}) -if GenerateEvent and GenerateEvent==true then -if self:IsAir()then -self:CreateEventCrash(timer.getTime(),DCSObject) -else -self:CreateEventDead(timer.getTime(),DCSObject) -end -elseif GenerateEvent==false then -else -self:CreateEventRemoveUnit(timer.getTime(),DCSObject) -end -USERFLAG:New(UnitGroupName):Set(100) -DCSObject:destroy() -end -return nil -end -function POSITIONABLE:GetDCSObject() -return nil -end -function POSITIONABLE:GetPosition() -self:F2(self.PositionableName) -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local PositionablePosition=DCSPositionable:getPosition() -self:T3(PositionablePosition) -return PositionablePosition -end -BASE:E({"Cannot GetPosition",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetOrientation() -local position=self:GetPosition() -if position then -return position.x,position.y,position.z -else -BASE:E({"Cannot GetOrientation",Positionable=self,Alive=self:IsAlive()}) -return nil,nil,nil -end -end -function POSITIONABLE:GetOrientationX() -local position=self:GetPosition() -if position then -return position.x -else -BASE:E({"Cannot GetOrientationX",Positionable=self,Alive=self:IsAlive()}) -return nil -end -end -function POSITIONABLE:GetOrientationY() -local position=self:GetPosition() -if position then -return position.y -else -BASE:E({"Cannot GetOrientationY",Positionable=self,Alive=self:IsAlive()}) -return nil -end -end -function POSITIONABLE:GetOrientationZ() -local position=self:GetPosition() -if position then -return position.z -else -BASE:E({"Cannot GetOrientationZ",Positionable=self,Alive=self:IsAlive()}) -return nil -end -end -function POSITIONABLE:GetPositionVec3() -self:F2(self.PositionableName) -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local PositionablePosition=DCSPositionable:getPosition().p -self:T3(PositionablePosition) -return PositionablePosition -end -BASE:E({"Cannot GetPositionVec3",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetVec3() -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local vec3=DCSPositionable:getPoint() -return vec3 -end -self:E({"Cannot get the Positionable DCS Object for GetVec3",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetVec2() -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local Vec3=DCSPositionable:getPoint() -return{x=Vec3.x,y=Vec3.z} -end -self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetPointVec2() -self:F2(self.PositionableName) -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local PositionableVec3=DCSPositionable:getPosition().p -local PositionablePointVec2=POINT_VEC2:NewFromVec3(PositionableVec3) -return PositionablePointVec2 -end -self:E({"Cannot GetPointVec2",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetPointVec3() -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local PositionableVec3=self:GetPositionVec3() -if false and self.pointvec3 then -self.pointvec3.x=PositionableVec3.x -self.pointvec3.y=PositionableVec3.y -self.pointvec3.z=PositionableVec3.z -else -self.pointvec3=POINT_VEC3:NewFromVec3(PositionableVec3) -end -return self.pointvec3 -end -BASE:E({"Cannot GetPointVec3",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetCoord() -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local PositionableVec3=self:GetVec3() -if self.coordinate then -self.coordinate:UpdateFromVec3(PositionableVec3) -else -self.coordinate=COORDINATE:NewFromVec3(PositionableVec3) -end -return self.coordinate -end -BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetCoordinate() -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local PositionableVec3=self:GetVec3() -local coord=COORDINATE:NewFromVec3(PositionableVec3) -local heading=self:GetHeading() -coord.Heading=heading -return coord -end -self:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:Explode(power,delay) -power=power or 100 -if delay and delay>0 then -self:ScheduleOnce(delay,POSITIONABLE.Explode,self,power,0) -else -local coord=self:GetCoord() -if coord then -coord:Explosion(power) -end -end -return self -end -function POSITIONABLE:GetOffsetCoordinate(x,y,z) -x=x or 0 -y=y or 0 -z=z or 0 -local X=self:GetOrientationX() -local Y=self:GetOrientationY() -local Z=self:GetOrientationZ() -local A={x=x,y=y,z=z} -local x={x=X.x*A.x,y=X.y*A.x,z=X.z*A.x} -local y={x=Y.x*A.y,y=Y.y*A.y,z=Y.z*A.y} -local z={x=Z.x*A.z,y=Z.y*A.z,z=Z.z*A.z} -local a={x=x.x+y.x+z.x,y=x.y+y.y+z.y,z=x.z+y.z+z.z} -local u=self:GetVec3() -local v={x=a.x+u.x,y=a.y+u.y,z=a.z+u.z} -local coord=COORDINATE:NewFromVec3(v) -return coord -end -function POSITIONABLE:GetRelativeCoordinate(x,y,z) -x=x or 0 -y=y or 0 -z=z or 0 -local selfPos=self:GetVec3() -local X=self:GetOrientationX() -local Y=self:GetOrientationY() -local Z=self:GetOrientationZ() -local off={ -x=x-selfPos.x, -y=y-selfPos.y, -z=z-selfPos.z -} -local res={x=0,y=0,z=0} -local mat={ -{X.x,Y.x,Z.x,off.x}, -{X.y,Y.y,Z.y,off.y}, -{X.z,Y.z,Z.z,off.z} -} -local m=3 -local n=4 -local h=1 -local k=1 -while h<=m and k<=n do -local v_max=math.abs(mat[h][k]) -local i_max=h -for i=h,m,1 do -local value=math.abs(mat[i][k]) -if value>v_max then -i_max=i -v_max=value -end -end -if mat[i_max][k]==0 then -k=k+1 -else -local tmp=mat[h] -mat[h]=mat[i_max] -mat[i_max]=tmp -for i=h+1,m,1 do -local f=mat[i][k]/mat[h][k] -mat[i][k]=0 -for j=k+1,n,1 do -mat[i][j]=mat[i][j]-f*mat[h][j] -end -end -h=h+1 -k=k+1 -end -end -res.z=mat[3][4]/mat[3][3] -res.y=(mat[2][4]-res.z*mat[2][3])/mat[2][2] -res.x=(mat[1][4]-res.y*mat[1][2]-res.z*mat[1][3])/mat[1][1] -local coord=COORDINATE:NewFromVec3(res) -return coord -end -function POSITIONABLE:GetRandomVec3(Radius) -self:F2(self.PositionableName) -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local PositionablePointVec3=DCSPositionable:getPosition().p -if Radius then -local PositionableRandomVec3={} -local angle=math.random()*math.pi*2 -PositionableRandomVec3.x=PositionablePointVec3.x+math.cos(angle)*math.random()*Radius -PositionableRandomVec3.y=PositionablePointVec3.y -PositionableRandomVec3.z=PositionablePointVec3.z+math.sin(angle)*math.random()*Radius -self:T3(PositionableRandomVec3) -return PositionableRandomVec3 -else -self:F("Radius is nil, returning the PointVec3 of the POSITIONABLE",PositionablePointVec3) -return PositionablePointVec3 -end -end -BASE:E({"Cannot GetRandomVec3",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetBoundingBox() -self:F2() -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local PositionableDesc=DCSPositionable:getDesc() -if PositionableDesc then -local PositionableBox=PositionableDesc.box -return PositionableBox -end -end -BASE:E({"Cannot GetBoundingBox",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetObjectSize() -local box=self:GetBoundingBox() -if box then -local x=box.max.x+math.abs(box.min.x) -local y=box.max.y+math.abs(box.min.y) -local z=box.max.z+math.abs(box.min.z) -return math.max(x,z),x,y,z -end -return 0,0,0,0 -end -function POSITIONABLE:GetBoundingRadius(MinDist) -self:F2() -local Box=self:GetBoundingBox() -local boxmin=MinDist or 0 -if Box then -local X=Box.max.x-Box.min.x -local Z=Box.max.z-Box.min.z -local CX=X/2 -local CZ=Z/2 -return math.max(math.max(CX,CZ),boxmin) -end -BASE:E({"Cannot GetBoundingRadius",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetAltitude() -self:F2() -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local PositionablePointVec3=DCSPositionable:getPoint() -return PositionablePointVec3.y -end -BASE:E({"Cannot GetAltitude",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:IsAboveRunway() -self:F2(self.PositionableName) -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local Vec2=self:GetVec2() -local SurfaceType=land.getSurfaceType(Vec2) -local IsAboveRunway=SurfaceType==land.SurfaceType.RUNWAY -self:T2(IsAboveRunway) -return IsAboveRunway -end -BASE:E({"Cannot IsAboveRunway",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetSize() -local DCSObject=self:GetDCSObject() -if DCSObject then -return 1 -else -return 0 -end -end -function POSITIONABLE:GetHeading() -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local PositionablePosition=DCSPositionable:getPosition() -if PositionablePosition then -local PositionableHeading=math.atan2(PositionablePosition.x.z,PositionablePosition.x.x) -if PositionableHeading<0 then -PositionableHeading=PositionableHeading+2*math.pi -end -PositionableHeading=PositionableHeading*180/math.pi -return PositionableHeading -end -end -self:E({"Cannot GetHeading",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:IsAir() -self:F2() -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitDescriptor=DCSUnit:getDesc() -self:T3({UnitDescriptor.category,Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) -local IsAirResult=(UnitDescriptor.category==Unit.Category.AIRPLANE)or(UnitDescriptor.category==Unit.Category.HELICOPTER) -self:T3(IsAirResult) -return IsAirResult -end -self:E({"Cannot check IsAir",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:IsGround() -self:F2() -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitDescriptor=DCSUnit:getDesc() -self:T3({UnitDescriptor.category,Unit.Category.GROUND_UNIT}) -local IsGroundResult=(UnitDescriptor.category==Unit.Category.GROUND_UNIT) -self:T3(IsGroundResult) -return IsGroundResult -end -self:E({"Cannot check IsGround",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:IsShip() -self:F2() -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitDescriptor=DCSUnit:getDesc() -self:T3({UnitDescriptor.category,Unit.Category.SHIP}) -local IsShipResult=(UnitDescriptor.category==Unit.Category.SHIP) -self:T3(IsShipResult) -return IsShipResult -end -self:E({"Cannot check IsShip",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:IsSubmarine() -self:F2() -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitDescriptor=DCSUnit:getDesc() -if UnitDescriptor.attributes["Submarines"]==true then -return true -else -return false -end -end -self:E({"Cannot check IsSubmarine",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:InAir() -self:F2(self.PositionableName) -return nil -end -function POSITIONABLE:GetVelocity() -self:F2(self.PositionableName) -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local Velocity=VELOCITY:New(self) -return Velocity -end -BASE:E({"Cannot GetVelocity",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetVelocityVec3() -self:F2(self.PositionableName) -local DCSPositionable=self:GetDCSObject() -if DCSPositionable and DCSPositionable:isExist()then -local PositionableVelocityVec3=DCSPositionable:getVelocity() -self:T3(PositionableVelocityVec3) -return PositionableVelocityVec3 -end -BASE:E({"Cannot GetVelocityVec3",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function POSITIONABLE:GetRelativeVelocity(Positionable) -self:F2(self.PositionableName) -local v1=self:GetVelocityVec3() -local v2=Positionable:GetVelocityVec3() -local vtot=UTILS.VecAdd(v1,v2) -return UTILS.VecNorm(vtot) -end -function POSITIONABLE:GetHeight() -self:F2(self.PositionableName) -local DCSPositionable=self:GetDCSObject() -if DCSPositionable and DCSPositionable:isExist()then -local PositionablePosition=DCSPositionable:getPosition() -if PositionablePosition then -local PositionableHeight=PositionablePosition.p.y -self:T2(PositionableHeight) -return PositionableHeight -end -end -return nil -end -function POSITIONABLE:GetVelocityKMH() -self:F2(self.PositionableName) -local DCSPositionable=self:GetDCSObject() -if DCSPositionable and DCSPositionable:isExist()then -local VelocityVec3=self:GetVelocityVec3() -local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5 -local Velocity=Velocity*3.6 -self:T3(Velocity) -return Velocity -end -return 0 -end -function POSITIONABLE:GetVelocityMPS() -self:F2(self.PositionableName) -local DCSPositionable=self:GetDCSObject() -if DCSPositionable and DCSPositionable:isExist()then -local VelocityVec3=self:GetVelocityVec3() -local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5 -self:T3(Velocity) -return Velocity -end -return 0 -end -function POSITIONABLE:GetVelocityKNOTS() -self:F2(self.PositionableName) -return UTILS.MpsToKnots(self:GetVelocityMPS()) -end -function POSITIONABLE:GetAirspeedTrue() -local tas=0 -local coord=self:GetCoord() -if coord then -local alt=coord.y -local wvec3=coord:GetWindVec3(alt,false) -local vvec3=self:GetVelocityVec3() -local tasvec3=UTILS.VecSubstract(vvec3,wvec3) -tas=UTILS.VecNorm(tasvec3) -end -return tas -end -function POSITIONABLE:GetAirspeedIndicated(oatcorr) -local tas=self:GetAirspeedTrue() -local altitude=self:GetAltitude() -local ias=UTILS.TasToIas(tas,altitude,oatcorr) -return ias -end -function POSITIONABLE:GetGroundSpeed() -local gs=0 -local vel=self:GetVelocityVec3() -if vel then -local vec2={x=vel.x,y=vel.z} -gs=UTILS.Vec2Norm(vel) -end -return gs -end -function POSITIONABLE:GetAoA() -local unitpos=self:GetPosition() -if unitpos then -local unitvel=self:GetVelocityVec3() -if unitvel and UTILS.VecNorm(unitvel)~=0 then -local wind=self:GetCoordinate():GetWindWithTurbulenceVec3() -unitvel.x=unitvel.x-wind.x -unitvel.y=unitvel.y-wind.y -unitvel.z=unitvel.z-wind.z -local AxialVel={} -AxialVel.x=UTILS.VecDot(unitpos.x,unitvel) -AxialVel.y=UTILS.VecDot(unitpos.y,unitvel) -AxialVel.z=UTILS.VecDot(unitpos.z,unitvel) -local AoA=math.acos(UTILS.VecDot({x=1,y=0,z=0},{x=AxialVel.x,y=AxialVel.y,z=0})/UTILS.VecNorm({x=AxialVel.x,y=AxialVel.y,z=0})) -if AxialVel.y>0 then -AoA=-AoA -end -return math.deg(AoA) -end -end -return nil -end -function POSITIONABLE:GetClimbAngle() -local unitpos=self:GetPosition() -if unitpos then -local unitvel=self:GetVelocityVec3() -if unitvel and UTILS.VecNorm(unitvel)~=0 then -local angle=math.asin(unitvel.y/UTILS.VecNorm(unitvel)) -return math.deg(angle) -else -return 0 -end -end -return nil -end -function POSITIONABLE:GetPitch() -local unitpos=self:GetPosition() -if unitpos then -return math.deg(math.asin(unitpos.x.y)) -end -return nil -end -function POSITIONABLE:GetRoll() -local unitpos=self:GetPosition() -if unitpos then -local cp=UTILS.VecCross(unitpos.x,{x=0,y=1,z=0}) -local dp=UTILS.VecDot(cp,unitpos.z) -local Roll=math.acos(dp/(UTILS.VecNorm(cp)*UTILS.VecNorm(unitpos.z))) -if unitpos.z.y>0 then -Roll=-Roll -end -return math.deg(Roll) -end -return nil -end -function POSITIONABLE:GetYaw() -local unitpos=self:GetPosition() -if unitpos then -local unitvel=self:GetVelocityVec3() -if unitvel and UTILS.VecNorm(unitvel)~=0 then -local AxialVel={} -AxialVel.x=UTILS.VecDot(unitpos.x,unitvel) -AxialVel.y=UTILS.VecDot(unitpos.y,unitvel) -AxialVel.z=UTILS.VecDot(unitpos.z,unitvel) -local Yaw=math.acos(UTILS.VecDot({x=1,y=0,z=0},{x=AxialVel.x,y=0,z=AxialVel.z})/UTILS.VecNorm({x=AxialVel.x,y=0,z=AxialVel.z})) -if AxialVel.z>0 then -Yaw=-Yaw -end -return Yaw -end -end -return nil -end -function POSITIONABLE:GetMessageText(Message,Name) -local DCSObject=self:GetDCSObject() -if DCSObject then -local Callsign=string.format("%s",((Name~=""and Name)or self:GetCallsign()~=""and self:GetCallsign())or self:GetName()) -local MessageText=string.format("%s - %s",Callsign,Message) -return MessageText -end -return nil -end -function POSITIONABLE:GetMessage(Message,Duration,Name) -local DCSObject=self:GetDCSObject() -if DCSObject then -local MessageText=self:GetMessageText(Message,Name) -return MESSAGE:New(MessageText,Duration) -end -return nil -end -function POSITIONABLE:GetMessageType(Message,MessageType,Name) -local DCSObject=self:GetDCSObject() -if DCSObject then -local MessageText=self:GetMessageText(Message,Name) -return MESSAGE:NewType(MessageText,MessageType) -end -return nil -end -function POSITIONABLE:MessageToAll(Message,Duration,Name) -self:F2({Message,Duration}) -local DCSObject=self:GetDCSObject() -if DCSObject then -self:GetMessage(Message,Duration,Name):ToAll() -end -return nil -end -function POSITIONABLE:MessageToCoalition(Message,Duration,MessageCoalition,Name) -self:F2({Message,Duration}) -local Name=Name or"" -local DCSObject=self:GetDCSObject() -if DCSObject then -self:GetMessage(Message,Duration,Name):ToCoalition(MessageCoalition) -end -return nil -end -function POSITIONABLE:MessageTypeToCoalition(Message,MessageType,MessageCoalition,Name) -self:F2({Message,MessageType}) -local Name=Name or"" -local DCSObject=self:GetDCSObject() -if DCSObject then -self:GetMessageType(Message,MessageType,Name):ToCoalition(MessageCoalition) -end -return nil -end -function POSITIONABLE:MessageToRed(Message,Duration,Name) -self:F2({Message,Duration}) -local DCSObject=self:GetDCSObject() -if DCSObject then -self:GetMessage(Message,Duration,Name):ToRed() -end -return nil -end -function POSITIONABLE:MessageToBlue(Message,Duration,Name) -self:F2({Message,Duration}) -local DCSObject=self:GetDCSObject() -if DCSObject then -self:GetMessage(Message,Duration,Name):ToBlue() -end -return nil -end -function POSITIONABLE:MessageToClient(Message,Duration,Client,Name) -self:F2({Message,Duration}) -local DCSObject=self:GetDCSObject() -if DCSObject then -self:GetMessage(Message,Duration,Name):ToClient(Client) -end -return nil -end -function POSITIONABLE:MessageToUnit(Message,Duration,MessageUnit,Name) -self:F2({Message,Duration}) -local DCSObject=self:GetDCSObject() -if DCSObject then -if DCSObject:isExist()then -if MessageUnit:IsAlive()then -self:GetMessage(Message,Duration,Name):ToUnit(MessageUnit) -else -BASE:E({"Message not sent to Unit; Unit is not alive...",Message=Message,MessageUnit=MessageUnit}) -end -else -BASE:E({"Message not sent to Unit; Positionable is not alive ...",Message=Message,Positionable=self,MessageUnit=MessageUnit}) -end -end -end -function POSITIONABLE:MessageToGroup(Message,Duration,MessageGroup,Name) -self:F2({Message,Duration}) -local DCSObject=self:GetDCSObject() -if DCSObject then -if DCSObject:isExist()then -if MessageGroup:IsAlive()then -self:GetMessage(Message,Duration,Name):ToGroup(MessageGroup) -else -BASE:E({"Message not sent to Group; Group is not alive...",Message=Message,MessageGroup=MessageGroup}) -end -else -BASE:E({ -"Message not sent to Group; Positionable is not alive ...", -Message=Message, -Positionable=self, -MessageGroup=MessageGroup -}) -end -end -return nil -end -function POSITIONABLE:MessageToUnit(Message,Duration,MessageUnit,Name) -self:F2({Message,Duration}) -local DCSObject=self:GetDCSObject() -if DCSObject then -if DCSObject:isExist()then -if MessageUnit:IsAlive()then -self:GetMessage(Message,Duration,Name):ToUnit(MessageUnit) -else -BASE:E({"Message not sent to Unit; Unit is not alive...",Message=Message,MessageUnit=MessageUnit}) -end -else -BASE:E({"Message not sent to Unit; Positionable is not alive ...",Message=Message,Positionable=self,MessageUnit=MessageUnit}) -end -end -return nil -end -function POSITIONABLE:MessageTypeToGroup(Message,MessageType,MessageGroup,Name) -self:F2({Message,MessageType}) -local DCSObject=self:GetDCSObject() -if DCSObject then -if DCSObject:isExist()then -self:GetMessageType(Message,MessageType,Name):ToGroup(MessageGroup) -end -end -return nil -end -function POSITIONABLE:MessageToSetGroup(Message,Duration,MessageSetGroup,Name) -self:F2({Message,Duration}) -local DCSObject=self:GetDCSObject() -if DCSObject then -if DCSObject:isExist()then -MessageSetGroup:ForEachGroupAlive(function(MessageGroup) -self:GetMessage(Message,Duration,Name):ToGroup(MessageGroup) -end) -end -end -return nil -end -function POSITIONABLE:MessageToSetUnit(Message,Duration,MessageSetUnit,Name) -self:F2({Message,Duration}) -local DCSObject=self:GetDCSObject() -if DCSObject then -if DCSObject:isExist()then -MessageSetUnit:ForEachUnit( -function(MessageGroup) -self:GetMessage(Message,Duration,Name):ToUnit(MessageGroup) -end -) -end -end -return nil -end -function POSITIONABLE:MessageToSetUnit(Message,Duration,MessageSetUnit,Name) -self:F2({Message,Duration}) -local DCSObject=self:GetDCSObject() -if DCSObject then -if DCSObject:isExist()then -MessageSetUnit:ForEachUnit( -function(MessageGroup) -self:GetMessage(Message,Duration,Name):ToUnit(MessageGroup) -end -) -end -end -return nil -end -function POSITIONABLE:Message(Message,Duration,Name) -self:F2({Message,Duration}) -local DCSObject=self:GetDCSObject() -if DCSObject then -self:GetMessage(Message,Duration,Name):ToGroup(self) -end -return nil -end -function POSITIONABLE:GetRadio() -self:F2(self) -return RADIO:New(self) -end -function POSITIONABLE:GetBeacon() -self:F2(self) -return BEACON:New(self) -end -function POSITIONABLE:LaseUnit(Target,LaserCode,Duration) -self:F2() -LaserCode=LaserCode or math.random(1000,9999) -local RecceDcsUnit=self:GetDCSObject() -local TargetVec3=Target:GetVec3() -self:F("building spot") -self.Spot=SPOT:New(self) -self.Spot:LaseOn(Target,LaserCode,Duration) -self.LaserCode=LaserCode -return self.Spot -end -function POSITIONABLE:LaseCoordinate(Coordinate,LaserCode,Duration) -self:F2() -LaserCode=LaserCode or math.random(1000,9999) -self.Spot=SPOT:New(self) -self.Spot:LaseOnCoordinate(Coordinate,LaserCode,Duration) -self.LaserCode=LaserCode -return self.Spot -end -function POSITIONABLE:LaseOff() -self:F2() -if self.Spot then -self.Spot:LaseOff() -self.Spot=nil -end -return self -end -function POSITIONABLE:IsLasing() -self:F2() -local Lasing=false -if self.Spot then -Lasing=self.Spot:IsLasing() -end -return Lasing -end -function POSITIONABLE:GetSpot() -return self.Spot -end -function POSITIONABLE:GetLaserCode() -return self.LaserCode -end -do -function POSITIONABLE:AddCargo(Cargo) -self.__.Cargo[Cargo]=Cargo -return self -end -function POSITIONABLE:GetCargo() -return self.__.Cargo -end -function POSITIONABLE:RemoveCargo(Cargo) -self.__.Cargo[Cargo]=nil -return self -end -function POSITIONABLE:HasCargo(Cargo) -return self.__.Cargo[Cargo] -end -function POSITIONABLE:ClearCargo() -self.__.Cargo={} -end -function POSITIONABLE:IsCargoEmpty() -local IsEmpty=true -for _,Cargo in pairs(self.__.Cargo)do -IsEmpty=false -break -end -return IsEmpty -end -function POSITIONABLE:CargoItemCount() -local ItemCount=0 -for CargoName,Cargo in pairs(self.__.Cargo)do -ItemCount=ItemCount+Cargo:GetCount() -end -return ItemCount -end -function POSITIONABLE:GetTroopCapacity() -local DCSunit=self:GetDCSObject() -local capacity=DCSunit:getDescentCapacity() -return capacity -end -function POSITIONABLE:GetCargoBayFreeWeight() -if not self.__.CargoBayWeightLimit then -self:SetCargoBayWeightLimit() -end -local CargoWeight=0 -for CargoName,Cargo in pairs(self.__.Cargo)do -CargoWeight=CargoWeight+Cargo:GetWeight() -end -return self.__.CargoBayWeightLimit-CargoWeight -end -POSITIONABLE.DefaultInfantryWeight=95 -POSITIONABLE.CargoBayCapacityValues={ -["Air"]={ -["C_130"]=70000, -}, -["Naval"]={ -["Type_071"]=245000, -["LHA_Tarawa"]=500000, -["Ropucha_class"]=150000, -["Dry_cargo_ship_1"]=70000, -["Dry_cargo_ship_2"]=70000, -["Higgins_boat"]=3700, -["USS_Samuel_Chase"]=25000, -["LST_Mk2"]=2100000, -["speedboat"]=500, -["Seawise_Giant"]=261000000, -}, -["Ground"]={ -["AAV7"]=25*POSITIONABLE.DefaultInfantryWeight, -["Bedford_MWD"]=8*POSITIONABLE.DefaultInfantryWeight, -["Blitz_36_6700A"]=10*POSITIONABLE.DefaultInfantryWeight, -["BMD_1"]=9*POSITIONABLE.DefaultInfantryWeight, -["BMP_1"]=8*POSITIONABLE.DefaultInfantryWeight, -["BMP_2"]=7*POSITIONABLE.DefaultInfantryWeight, -["BMP_3"]=8*POSITIONABLE.DefaultInfantryWeight, -["Boman"]=25*POSITIONABLE.DefaultInfantryWeight, -["BTR_80"]=9*POSITIONABLE.DefaultInfantryWeight, -["BTR_82A"]=9*POSITIONABLE.DefaultInfantryWeight, -["BTR_D"]=12*POSITIONABLE.DefaultInfantryWeight, -["Cobra"]=8*POSITIONABLE.DefaultInfantryWeight, -["Land_Rover_101_FC"]=11*POSITIONABLE.DefaultInfantryWeight, -["Land_Rover_109_S3"]=7*POSITIONABLE.DefaultInfantryWeight, -["LAV_25"]=6*POSITIONABLE.DefaultInfantryWeight, -["M_2_Bradley"]=6*POSITIONABLE.DefaultInfantryWeight, -["M1043_HMMWV_Armament"]=4*POSITIONABLE.DefaultInfantryWeight, -["M1045_HMMWV_TOW"]=4*POSITIONABLE.DefaultInfantryWeight, -["M1126_Stryker_ICV"]=9*POSITIONABLE.DefaultInfantryWeight, -["M1134_Stryker_ATGM"]=9*POSITIONABLE.DefaultInfantryWeight, -["M2A1_halftrack"]=9*POSITIONABLE.DefaultInfantryWeight, -["M_113"]=9*POSITIONABLE.DefaultInfantryWeight, -["Marder"]=6*POSITIONABLE.DefaultInfantryWeight, -["MCV_80"]=9*POSITIONABLE.DefaultInfantryWeight, -["MLRS_FDDM"]=4*POSITIONABLE.DefaultInfantryWeight, -["MTLB"]=25*POSITIONABLE.DefaultInfantryWeight, -["GAZ_66"]=8*POSITIONABLE.DefaultInfantryWeight, -["GAZ_3307"]=12*POSITIONABLE.DefaultInfantryWeight, -["GAZ_3308"]=14*POSITIONABLE.DefaultInfantryWeight, -["Grad_FDDM"]=6*POSITIONABLE.DefaultInfantryWeight, -["KAMAZ_Truck"]=12*POSITIONABLE.DefaultInfantryWeight, -["KrAZ6322"]=12*POSITIONABLE.DefaultInfantryWeight, -["M_818"]=12*POSITIONABLE.DefaultInfantryWeight, -["Tigr_233036"]=6*POSITIONABLE.DefaultInfantryWeight, -["TPZ"]=10*POSITIONABLE.DefaultInfantryWeight, -["UAZ_469"]=4*POSITIONABLE.DefaultInfantryWeight, -["Ural_375"]=12*POSITIONABLE.DefaultInfantryWeight, -["Ural_4320_31"]=14*POSITIONABLE.DefaultInfantryWeight, -["Ural_4320_APA_5D"]=10*POSITIONABLE.DefaultInfantryWeight, -["Ural_4320T"]=14*POSITIONABLE.DefaultInfantryWeight, -["ZBD04A"]=7*POSITIONABLE.DefaultInfantryWeight, -["VAB_Mephisto"]=8*POSITIONABLE.DefaultInfantryWeight, -["tt_KORD"]=6*POSITIONABLE.DefaultInfantryWeight, -["tt_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, -["HL_KORD"]=6*POSITIONABLE.DefaultInfantryWeight, -["HL_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, -["CCKW_353"]=16*POSITIONABLE.DefaultInfantryWeight, -} -} -function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit) -if WeightLimit then -self.__.CargoBayWeightLimit=WeightLimit -elseif self.__.CargoBayWeightLimit~=nil then -else -local Desc=self:GetDesc() -self:F({Desc=Desc}) -local TypeName=Desc.typeName or"Unknown Type" -TypeName=string.gsub(TypeName,"[%p%s]","_") -if self:IsAir()then -local Weights=POSITIONABLE.CargoBayCapacityValues.Air -local massMax=Desc.massMax or 0 -local maxTakeoff=Weights[TypeName] -if maxTakeoff then -massMax=maxTakeoff -end -local massEmpty=Desc.massEmpty or 0 -local massFuelMax=Desc.fuelMassMax or 0 -local relFuel=math.min(self:GetFuel()or 1.0,1.0) -local massFuel=massFuelMax*relFuel -local CargoWeight=massMax-(massEmpty+massFuel) -self:T(string.format("Setting Cargo bay weight limit [%s]=%d kg (Mass max=%d, empty=%d, fuelMax=%d kg (rel=%.3f), fuel=%d kg",TypeName,CargoWeight,massMax,massEmpty,massFuelMax,relFuel,massFuel)) -self.__.CargoBayWeightLimit=CargoWeight -elseif self:IsShip()then -local Weights=POSITIONABLE.CargoBayCapacityValues.Naval -self.__.CargoBayWeightLimit=(Weights[TypeName]or 50000) -else -local Weights=POSITIONABLE.CargoBayCapacityValues.Ground -local CargoBayWeightLimit=(Weights[TypeName]or 0) -self.__.CargoBayWeightLimit=CargoBayWeightLimit -end -end -self:F({CargoBayWeightLimit=self.__.CargoBayWeightLimit}) -end -function POSITIONABLE:GetCargoBayWeightLimit() -if self.__.CargoBayWeightLimit==nil then -self:SetCargoBayWeightLimit() -end -return self.__.CargoBayWeightLimit -end -end -function POSITIONABLE:Flare(FlareColor) -self:F2() -trigger.action.signalFlare(self:GetVec3(),FlareColor,0) -end -function POSITIONABLE:FlareWhite() -self:F2() -trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.White,0) -end -function POSITIONABLE:FlareYellow() -self:F2() -trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.Yellow,0) -end -function POSITIONABLE:FlareGreen() -self:F2() -trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.Green,0) -end -function POSITIONABLE:FlareRed() -self:F2() -local Vec3=self:GetVec3() -if Vec3 then -trigger.action.signalFlare(Vec3,trigger.flareColor.Red,0) -end -end -function POSITIONABLE:Smoke(SmokeColor,Range,AddHeight) -self:F2() -if Range then -local Vec3=self:GetRandomVec3(Range) -Vec3.y=Vec3.y+AddHeight or 0 -trigger.action.smoke(Vec3,SmokeColor) -else -local Vec3=self:GetVec3() -Vec3.y=Vec3.y+AddHeight or 0 -trigger.action.smoke(self:GetVec3(),SmokeColor) -end -end -function POSITIONABLE:SmokeGreen() -self:F2() -trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Green) -end -function POSITIONABLE:SmokeRed() -self:F2() -trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Red) -end -function POSITIONABLE:SmokeWhite() -self:F2() -trigger.action.smoke(self:GetVec3(),trigger.smokeColor.White) -end -function POSITIONABLE:SmokeOrange() -self:F2() -trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Orange) -end -function POSITIONABLE:SmokeBlue() -self:F2() -trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Blue) -end -function POSITIONABLE:IsInZone(Zone) -self:F2({self.PositionableName,Zone}) -if self:IsAlive()then -local IsInZone=Zone:IsVec3InZone(self:GetVec3()) -return IsInZone -end -return false -end -function POSITIONABLE:IsNotInZone(Zone) -self:F2({self.PositionableName,Zone}) -if self:IsAlive()then -local IsNotInZone=not Zone:IsVec3InZone(self:GetVec3()) -return IsNotInZone -else -return false -end -end -CONTROLLABLE={ -ClassName="CONTROLLABLE", -ControllableName="", -WayPointFunctions={}, -} -function CONTROLLABLE:New(ControllableName) -local self=BASE:Inherit(self,POSITIONABLE:New(ControllableName)) -self.ControllableName=ControllableName -self.TaskScheduler=SCHEDULER:New(self) -return self -end -function CONTROLLABLE:_GetController() -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local ControllableController=DCSControllable:getController() -return ControllableController -end -return nil -end -function CONTROLLABLE:GetLife() -self:F2(self.ControllableName) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local UnitLife=0 -local Units=self:GetUnits() -if#Units==1 then -local Unit=Units[1] -UnitLife=Unit:GetLife() -else -local UnitLifeTotal=0 -for UnitID,Unit in pairs(Units)do -local Unit=Unit -UnitLifeTotal=UnitLifeTotal+Unit:GetLife() -end -UnitLife=UnitLifeTotal/#Units -end -return UnitLife -end -return nil -end -function CONTROLLABLE:GetLife0() -self:F2(self.ControllableName) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local UnitLife=0 -local Units=self:GetUnits() -if#Units==1 then -local Unit=Units[1] -UnitLife=Unit:GetLife0() -else -local UnitLifeTotal=0 -for UnitID,Unit in pairs(Units)do -local Unit=Unit -UnitLifeTotal=UnitLifeTotal+Unit:GetLife0() -end -UnitLife=UnitLifeTotal/#Units -end -return UnitLife -end -return nil -end -function CONTROLLABLE:GetFuelMin() -self:F(self.ControllableName) -return nil -end -function CONTROLLABLE:GetFuelAve() -self:F(self.ControllableName) -return nil -end -function CONTROLLABLE:GetFuel() -self:F(self.ControllableName) -return nil -end -function CONTROLLABLE:ClearTasks() -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -Controller:resetTask() -return self -end -return nil -end -function CONTROLLABLE:PopCurrentTask() -self:F2() -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -Controller:popTask() -return self -end -return nil -end -function CONTROLLABLE:PushTask(DCSTask,WaitTime) -self:F2() -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local DCSControllableName=self:GetName() -local function PushTask(Controller,DCSTask) -if self and self:IsAlive()then -local Controller=self:_GetController() -Controller:pushTask(DCSTask) -else -BASE:E({DCSControllableName.." is not alive anymore.",DCSTask=DCSTask}) -end -end -if not WaitTime or WaitTime==0 then -PushTask(self,DCSTask) -else -self.TaskScheduler:Schedule(self,PushTask,{DCSTask},WaitTime) -end -return self -end -return nil -end -function CONTROLLABLE:SetTask(DCSTask,WaitTime) -self:F({"SetTask",WaitTime,DCSTask=DCSTask}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local DCSControllableName=self:GetName() -self:T2("Controllable Name = "..DCSControllableName) -local function SetTask(Controller,DCSTask) -if self and self:IsAlive()then -local Controller=self:_GetController() -Controller:setTask(DCSTask) -self:T({ControllableName=self:GetName(),DCSTask=DCSTask}) -else -BASE:E({DCSControllableName.." is not alive anymore.",DCSTask=DCSTask}) -end -end -if not WaitTime or WaitTime==0 then -SetTask(self,DCSTask) -self:T({ControllableName=self:GetName(),DCSTask=DCSTask}) -else -self.TaskScheduler:Schedule(self,SetTask,{DCSTask},WaitTime) -end -return self -end -return nil -end -function CONTROLLABLE:HasTask() -local HasTaskResult=false -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -HasTaskResult=Controller:hasTask() -end -return HasTaskResult -end -function CONTROLLABLE:TaskCondition(time,userFlag,userFlagValue,condition,duration,lastWayPoint) -local DCSStopCondition={} -DCSStopCondition.time=time -DCSStopCondition.userFlag=userFlag -DCSStopCondition.userFlagValue=userFlagValue -DCSStopCondition.condition=condition -DCSStopCondition.duration=duration -DCSStopCondition.lastWayPoint=lastWayPoint -return DCSStopCondition -end -function CONTROLLABLE:TaskControlled(DCSTask,DCSStopCondition) -local DCSTaskControlled={ -id='ControlledTask', -params={ -task=DCSTask, -stopCondition=DCSStopCondition, -}, -} -return DCSTaskControlled -end -function CONTROLLABLE:TaskCombo(DCSTasks) -local DCSTaskCombo={ -id='ComboTask', -params={ -tasks=DCSTasks, -}, -} -return DCSTaskCombo -end -function CONTROLLABLE:TaskWrappedAction(DCSCommand,Index) -local DCSTaskWrappedAction={ -id="WrappedAction", -enabled=true, -number=Index or 1, -auto=false, -params={ -action=DCSCommand, -}, -} -return DCSTaskWrappedAction -end -function CONTROLLABLE:TaskEmptyTask() -local DCSTaskWrappedAction={ -["id"]="WrappedAction", -["params"]={ -["action"]={ -["id"]="Script", -["params"]={ -["command"]="", -}, -}, -}, -} -return DCSTaskWrappedAction -end -function CONTROLLABLE:SetTaskWaypoint(Waypoint,Task) -Waypoint.task=self:TaskCombo({Task}) -self:F({Waypoint.task}) -return Waypoint.task -end -function CONTROLLABLE:SetCommand(DCSCommand) -self:F2(DCSCommand) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -Controller:setCommand(DCSCommand) -return self -end -return nil -end -function CONTROLLABLE:CommandSwitchWayPoint(FromWayPoint,ToWayPoint) -self:F2({FromWayPoint,ToWayPoint}) -local CommandSwitchWayPoint={ -id='SwitchWaypoint', -params={ -fromWaypointIndex=FromWayPoint, -goToWaypointIndex=ToWayPoint, -}, -} -self:T3({CommandSwitchWayPoint}) -return CommandSwitchWayPoint -end -function CONTROLLABLE:CommandStopRoute(StopRoute) -self:F2({StopRoute}) -local CommandStopRoute={ -id='StopRoute', -params={ -value=StopRoute, -}, -} -self:T3({CommandStopRoute}) -return CommandStopRoute -end -function CONTROLLABLE:StartUncontrolled(delay) -if delay and delay>0 then -SCHEDULER:New(nil,CONTROLLABLE.StartUncontrolled,{self},delay) -else -self:SetCommand({id='Start',params={}}) -end -return self -end -function CONTROLLABLE:CommandActivateBeacon(Type,System,Frequency,UnitID,Channel,ModeChannel,AA,Callsign,Bearing,Delay) -AA=AA or self:IsAir() -UnitID=UnitID or self:GetID() -local CommandActivateBeacon={ -id="ActivateBeacon", -params={ -["type"]=Type, -["system"]=System, -["frequency"]=Frequency, -["unitId"]=UnitID, -["channel"]=Channel, -["modeChannel"]=ModeChannel, -["AA"]=AA, -["callsign"]=Callsign, -["bearing"]=Bearing, -}, -} -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandActivateBeacon,{self,Type,System,Frequency,UnitID,Channel,ModeChannel,AA,Callsign,Bearing},Delay) -else -self:SetCommand(CommandActivateBeacon) -end -return self -end -function CONTROLLABLE:CommandActivateACLS(UnitID,Name,Delay) -local CommandActivateACLS={ -id='ActivateACLS', -params={ -unitId=UnitID or self:GetID(), -name=Name or"ACL", -} -} -self:T({CommandActivateACLS}) -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandActivateACLS,{self,UnitID,Name},Delay) -else -local controller=self:_GetController() -controller:setCommand(CommandActivateACLS) -end -return self -end -function CONTROLLABLE:CommandDeactivateACLS(Delay) -local CommandDeactivateACLS={ -id='DeactivateACLS', -params={} -} -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandDeactivateACLS,{self},Delay) -else -local controller=self:_GetController() -controller:setCommand(CommandDeactivateACLS) -end -return self -end -function CONTROLLABLE:CommandActivateICLS(Channel,UnitID,Callsign,Delay) -local CommandActivateICLS={ -id="ActivateICLS", -params={ -["type"]=BEACON.Type.ICLS, -["channel"]=Channel, -["unitId"]=UnitID or self:GetID(), -["callsign"]=Callsign, -}, -} -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandActivateICLS,{self,Channel,UnitID,Callsign},Delay) -else -self:SetCommand(CommandActivateICLS) -end -return self -end -function CONTROLLABLE:CommandActivateLink4(Frequency,UnitID,Callsign,Delay) -local freq=Frequency or 336 -local CommandActivateLink4={ -id="ActivateLink4", -params={ -["frequency"]=freq*1000000, -["unitId"]=UnitID or self:GetID(), -["name"]=Callsign or"LNK", -} -} -self:T({CommandActivateLink4}) -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandActivateLink4,{self,Frequency,UnitID,Callsign},Delay) -else -local controller=self:_GetController() -controller:setCommand(CommandActivateLink4) -end -return self -end -function CONTROLLABLE:CommandDeactivateBeacon(Delay) -local CommandDeactivateBeacon={id='DeactivateBeacon',params={}} -local CommandDeactivateBeacon={id='DeactivateBeacon',params={}} -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandDeactivateBeacon,{self},Delay) -else -self:SetCommand(CommandDeactivateBeacon) -end -return self -end -function CONTROLLABLE:CommandDeactivateLink4(Delay) -local CommandDeactivateLink4={id='DeactivateLink4',params={}} -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandDeactivateLink4,{self},Delay) -else -local controller=self:_GetController() -controller:setCommand(CommandDeactivateLink4) -end -return self -end -function CONTROLLABLE:CommandDeactivateICLS(Delay) -local CommandDeactivateICLS={id='DeactivateICLS',params={}} -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandDeactivateICLS,{self},Delay) -else -self:SetCommand(CommandDeactivateICLS) -end -return self -end -function CONTROLLABLE:CommandSetCallsign(CallName,CallNumber,Delay) -local CommandSetCallsign={id='SetCallsign',params={callname=CallName,number=CallNumber or 1}} -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandSetCallsign,{self,CallName,CallNumber},Delay) -else -self:SetCommand(CommandSetCallsign) -end -return self -end -function CONTROLLABLE:CommandEPLRS(SwitchOnOff,Delay) -if SwitchOnOff==nil then -SwitchOnOff=true -end -local CommandEPLRS={ -id='EPLRS', -params={ -value=SwitchOnOff, -groupId=self:GetID(), -}, -} -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandEPLRS,{self,SwitchOnOff},Delay) -else -self:T(string.format("EPLRS=%s for controllable %s (id=%s)",tostring(SwitchOnOff),tostring(self:GetName()),tostring(self:GetID()))) -self:SetCommand(CommandEPLRS) -end -return self -end -function CONTROLLABLE:CommandSetUnlimitedFuel(OnOff,Delay) -local CommandSetFuel={ -id='SetUnlimitedFuel', -params={ -value=OnOff -} -} -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandSetUnlimitedFuel,{self,OnOff},Delay) -else -self:SetCommand(CommandSetFuel) -end -return self -end -function CONTROLLABLE:CommandSetFrequency(Frequency,Modulation,Power,Delay) -local CommandSetFrequency={ -id='SetFrequency', -params={ -frequency=Frequency*1000000, -modulation=Modulation or radio.modulation.AM, -power=Power or 10, -}, -} -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandSetFrequency,{self,Frequency,Modulation,Power}) -else -self:SetCommand(CommandSetFrequency) -end -return self -end -function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,UnitID,Delay) -local CommandSetFrequencyForUnit={ -id='SetFrequencyForUnit', -params={ -frequency=Frequency*1000000, -modulation=Modulation or radio.modulation.AM, -unitId=UnitID or self:GetID(), -power=Power or 10, -}, -} -if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID}) -else -self:SetCommand(CommandSetFrequencyForUnit) -end -return self -end -function CONTROLLABLE:TaskEPLRS(SwitchOnOff,idx) -if SwitchOnOff==nil then -SwitchOnOff=true -end -local CommandEPLRS={ -id='EPLRS', -params={ -value=SwitchOnOff, -groupId=self:GetID(), -}, -} -return self:TaskWrappedAction(CommandEPLRS,idx or 1) -end -function CONTROLLABLE:TaskAttackGroup(AttackGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit,GroupAttack) -local DCSTask={id='AttackGroup', -params={ -groupId=AttackGroup:GetID(), -weaponType=WeaponType or 1073741822, -expend=WeaponExpend or"Auto", -attackQtyLimit=AttackQty and true or false, -attackQty=AttackQty or 1, -directionEnabled=Direction and true or false, -direction=Direction and math.rad(Direction)or 0, -altitudeEnabled=Altitude and true or false, -altitude=Altitude, -groupAttack=GroupAttack and true or false, -}, -} -return DCSTask -end -function CONTROLLABLE:TaskAttackUnit(AttackUnit,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType) -local DCSTask={ -id='AttackUnit', -params={ -unitId=AttackUnit:GetID(), -groupAttack=GroupAttack and GroupAttack or false, -expend=WeaponExpend or"Auto", -directionEnabled=Direction and true or false, -direction=Direction and math.rad(Direction)or 0, -altitudeEnabled=Altitude and true or false, -altitude=Altitude, -attackQtyLimit=AttackQty and true or false, -attackQty=AttackQty, -weaponType=WeaponType or 1073741822, -}, -} -return DCSTask -end -function CONTROLLABLE:TaskBombing(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType,Divebomb) -local DCSTask={ -id='Bombing', -params={ -point=Vec2, -x=Vec2.x, -y=Vec2.y, -groupAttack=GroupAttack and GroupAttack or false, -expend=WeaponExpend or"Auto", -attackQtyLimit=AttackQty and true or false, -attackQty=AttackQty or 1, -directionEnabled=Direction and true or false, -direction=Direction and math.rad(Direction)or 0, -altitudeEnabled=Altitude and true or false, -altitude=Altitude or 2000, -weaponType=WeaponType or 1073741822, -attackType=Divebomb and"Dive"or nil, -}, -} -return DCSTask -end -function CONTROLLABLE:TaskStrafing(Vec2,AttackQty,Length,WeaponType,WeaponExpend,Direction,GroupAttack) -local DCSTask={ -id='Strafing', -params={ -point=Vec2, -weaponType=WeaponType or 1073741822, -expend=WeaponExpend or"Auto", -attackQty=AttackQty or 1, -attackQtyLimit=AttackQty>1 and true or false, -direction=Direction and math.rad(Direction)or 0, -directionEnabled=Direction and true or false, -groupAttack=GroupAttack or false, -length=Length, -} -} -return DCSTask -end -function CONTROLLABLE:TaskAttackMapObject(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType) -local DCSTask={ -id='AttackMapObject', -params={ -point=Vec2, -x=Vec2.x, -y=Vec2.y, -groupAttack=GroupAttack or false, -expend=WeaponExpend or"Auto", -attackQtyLimit=AttackQty and true or false, -directionEnabled=Direction and true or false, -direction=Direction and math.rad(Direction)or 0, -altitudeEnabled=Altitude and true or false, -altitude=Altitude, -weaponType=WeaponType or 1073741822, -}, -} -return DCSTask -end -function CONTROLLABLE:TaskCarpetBombing(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType,CarpetLength) -local DCSTask={ -id='CarpetBombing', -params={ -attackType="Carpet", -x=Vec2.x, -y=Vec2.y, -groupAttack=GroupAttack and GroupAttack or false, -carpetLength=CarpetLength or 500, -weaponType=WeaponType or ENUMS.WeaponFlag.AnyBomb, -expend=WeaponExpend or"All", -attackQtyLimit=AttackQty and true or false, -attackQty=AttackQty or 1, -directionEnabled=Direction and true or false, -direction=Direction and math.rad(Direction)or 0, -altitudeEnabled=Altitude and true or false, -altitude=Altitude, -}, -} -return DCSTask -end -function CONTROLLABLE:TaskFollowBigFormation(FollowControllable,Vec3,LastWaypointIndex) -local DCSTask={ -id='FollowBigFormation', -params={ -groupId=FollowControllable:GetID(), -pos=Vec3, -lastWptIndexFlag=LastWaypointIndex and true or false, -lastWptIndex=LastWaypointIndex, -}, -} -return DCSTask -end -function CONTROLLABLE:TaskEmbarking(Coordinate,GroupSetForEmbarking,Duration,Distribution) -local g4e={} -if GroupSetForEmbarking then -for _,_group in pairs(GroupSetForEmbarking:GetSet())do -local group=_group -table.insert(g4e,group:GetID()) -end -else -self:E("ERROR: No groups for embarking specified!") -return nil -end -local groupID=self and self:GetID() -local DCSTask={ -id='Embarking', -params={ -selectedTransport=groupID, -x=Coordinate.x, -y=Coordinate.z, -groupsForEmbarking=g4e, -durationFlag=Duration and true or false, -duration=Duration, -distributionFlag=Distribution and true or false, -distribution=Distribution, -}, -} -return DCSTask -end -function CONTROLLABLE:TaskEmbarkToTransport(Coordinate,Radius,UnitType) -local EmbarkToTransport={ -id="EmbarkToTransport", -params={ -x=Coordinate.x, -y=Coordinate.z, -zoneRadius=Radius or 200, -selectedType=UnitType, -}, -} -return EmbarkToTransport -end -function CONTROLLABLE:TaskDisembarking(Coordinate,GroupSetToDisembark) -local g4e={} -if GroupSetToDisembark then -for _,_group in pairs(GroupSetToDisembark:GetSet())do -local group=_group -table.insert(g4e,group:GetID()) -end -else -self:E("ERROR: No groups for disembarking specified!") -return nil -end -local Disembarking={ -id="Disembarking", -params={ -x=Coordinate.x, -y=Coordinate.z, -groupsForEmbarking=g4e, -}, -} -return Disembarking -end -function CONTROLLABLE:TaskOrbitCircleAtVec2(Point,Altitude,Speed) -self:F2({self.ControllableName,Point,Altitude,Speed}) -local DCSTask={ -id='Orbit', -params={ -pattern=AI.Task.OrbitPattern.CIRCLE, -point=Point, -speed=Speed, -altitude=Altitude+land.getHeight(Point), -}, -} -return DCSTask -end -function CONTROLLABLE:TaskOrbit(Coord,Altitude,Speed,CoordRaceTrack) -local Pattern=AI.Task.OrbitPattern.CIRCLE -local P1={x=Coord.x,y=Coord.z or Coord.y} -local P2=nil -if CoordRaceTrack then -Pattern=AI.Task.OrbitPattern.RACE_TRACK -P2={x=CoordRaceTrack.x,y=CoordRaceTrack.z or CoordRaceTrack.y} -end -local Task={ -id='Orbit', -params={ -pattern=Pattern, -point=P1, -point2=P2, -speed=Speed or UTILS.KnotsToMps(250), -altitude=Altitude or Coord.y, -}, -} -return Task -end -function CONTROLLABLE:TaskOrbitCircle(Altitude,Speed,Coordinate) -self:F2({self.ControllableName,Altitude,Speed}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local OrbitVec2=Coordinate and Coordinate:GetVec2()or self:GetVec2() -return self:TaskOrbitCircleAtVec2(OrbitVec2,Altitude,Speed) -end -return nil -end -function CONTROLLABLE:TaskHoldPosition() -self:F2({self.ControllableName}) -return self:TaskOrbitCircle(30,10) -end -function CONTROLLABLE:TaskBombingRunway(Airbase,WeaponType,WeaponExpend,AttackQty,Direction,GroupAttack) -local DCSTask={ -id='BombingRunway', -params={ -runwayId=Airbase:GetID(), -weaponType=WeaponType or ENUMS.WeaponFlag.AnyBomb, -expend=WeaponExpend or AI.Task.WeaponExpend.ALL, -attackQty=AttackQty or 1, -direction=Direction and math.rad(Direction)or 0, -groupAttack=GroupAttack and true or false, -}, -} -return DCSTask -end -function CONTROLLABLE:TaskRefueling() -local DCSTask={ -id='Refueling', -params={}, -} -return DCSTask -end -function CONTROLLABLE:TaskRecoveryTanker(CarrierGroup,Speed,Altitude,LastWptNumber) -local LastWptFlag=type(LastWptNumber)=="number"and true or false -local DCSTask={ -id="RecoveryTanker", -params={ -groupId=CarrierGroup:GetID(), -speed=Speed, -altitude=Altitude, -lastWptIndexFlag=LastWptFlag, -lastWptIndex=LastWptNumber -} -} -return DCSTask -end -function CONTROLLABLE:TaskLandAtVec2(Vec2,Duration) -local DCSTask={ -id='Land', -params={ -point=Vec2, -durationFlag=Duration and true or false, -duration=Duration, -}, -} -return DCSTask -end -function CONTROLLABLE:TaskLandAtZone(Zone,Duration,RandomPoint) -local Point=RandomPoint and Zone:GetRandomVec2()or Zone:GetVec2() -local DCSTask=CONTROLLABLE.TaskLandAtVec2(self,Point,Duration) -return DCSTask -end -function CONTROLLABLE:TaskFollow(FollowControllable,Vec3,LastWaypointIndex) -self:F2({self.ControllableName,FollowControllable,Vec3,LastWaypointIndex}) -local LastWaypointIndexFlag=false -local lastWptIndexFlagChangedManually=false -if LastWaypointIndex then -LastWaypointIndexFlag=true -lastWptIndexFlagChangedManually=true -end -local DCSTask={ -id='Follow', -params={ -groupId=FollowControllable:GetID(), -pos=Vec3, -lastWptIndexFlag=LastWaypointIndexFlag, -lastWptIndex=LastWaypointIndex, -lastWptIndexFlagChangedManually=lastWptIndexFlagChangedManually, -}, -} -self:T3({DCSTask}) -return DCSTask -end -function CONTROLLABLE:TaskGroundEscort(FollowControllable,LastWaypointIndex,OrbitDistance,TargetTypes) -local DCSTask={ -id='GroundEscort', -params={ -groupId=FollowControllable and FollowControllable:GetID()or nil, -engagementDistMax=OrbitDistance or 2000, -lastWptIndexFlag=LastWaypointIndex and true or false, -lastWptIndex=LastWaypointIndex, -targetTypes=TargetTypes or{"Ground vehicles"}, -lastWptIndexFlagChangedManually=true, -}, -} -return DCSTask -end -function CONTROLLABLE:TaskEscort(FollowControllable,Vec3,LastWaypointIndex,EngagementDistance,TargetTypes) -local DCSTask={ -id='Escort', -params={ -groupId=FollowControllable and FollowControllable:GetID()or nil, -pos=Vec3, -lastWptIndexFlag=LastWaypointIndex and true or false, -lastWptIndex=LastWaypointIndex, -engagementDistMax=EngagementDistance, -targetTypes=TargetTypes or{"Air"}, -}, -} -return DCSTask -end -function CONTROLLABLE:TaskFireAtPoint(Vec2,Radius,AmmoCount,WeaponType,Altitude,ASL) -local DCSTask={ -id='FireAtPoint', -params={ -point=Vec2, -x=Vec2.x, -y=Vec2.y, -zoneRadius=Radius, -radius=Radius, -expendQty=1, -expendQtyEnabled=false, -alt_type=ASL and 0 or 1, -}, -} -if AmmoCount then -DCSTask.params.expendQty=AmmoCount -DCSTask.params.expendQtyEnabled=true -end -if Altitude then -DCSTask.params.altitude=Altitude -end -if WeaponType then -DCSTask.params.weaponType=WeaponType -end -return DCSTask -end -function CONTROLLABLE:TaskHold() -local DCSTask={id='Hold',params={}} -return DCSTask -end -function CONTROLLABLE:TaskFAC_AttackGroup(AttackGroup,WeaponType,Designation,Datalink,Frequency,Modulation,CallsignName,CallsignNumber) -local DCSTask={ -id='FAC_AttackGroup', -params={ -groupId=AttackGroup:GetID(), -weaponType=WeaponType or ENUMS.WeaponFlag.AutoDCS, -designation=Designation or"Auto", -datalink=Datalink and Datalink or true, -frequency=(Frequency or 133)*1000000, -modulation=Modulation or radio.modulation.AM, -callname=CallsignName, -number=CallsignNumber, -}, -} -return DCSTask -end -function CONTROLLABLE:EnRouteTaskEngageTargets(Distance,TargetTypes,Priority) -local DCSTask={ -id='EngageTargets', -params={ -maxDistEnabled=Distance and true or false, -maxDist=Distance, -targetTypes=TargetTypes or{"Air"}, -priority=Priority or 0, -}, -} -return DCSTask -end -function CONTROLLABLE:EnRouteTaskEngageTargetsInZone(Vec2,Radius,TargetTypes,Priority) -local DCSTask={ -id='EngageTargetsInZone', -params={ -point=Vec2, -zoneRadius=Radius, -targetTypes=TargetTypes or{"Air"}, -priority=Priority or 0 -}, -} -return DCSTask -end -function CONTROLLABLE:EnRouteTaskAntiShip(TargetTypes,Priority) -local DCSTask={ -id='EngageTargets', -key="AntiShip", -params={ -targetTypes=TargetTypes or{"Ships"}, -priority=Priority or 0 -} -} -return DCSTask -end -function CONTROLLABLE:EnRouteTaskSEAD(TargetTypes,Priority) -local DCSTask={ -id='EngageTargets', -key="SEAD", -params={ -targetTypes=TargetTypes or{"Air Defence"}, -priority=Priority or 0 -} -} -return DCSTask -end -function CONTROLLABLE:EnRouteTaskCAP(TargetTypes,Priority) -local DCSTask={ -id='EngageTargets', -key="CAP", -enabled=true, -params={ -targetTypes=TargetTypes or{"Air"}, -priority=Priority or 0 -} -} -return DCSTask -end -function CONTROLLABLE:EnRouteTaskEngageGroup(AttackGroup,Priority,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit) -local DCSTask={ -id='EngageGroup', -params={ -groupId=AttackGroup:GetID(), -weaponType=WeaponType, -expend=WeaponExpend or"Auto", -directionEnabled=Direction and true or false, -direction=Direction, -altitudeEnabled=Altitude and true or false, -altitude=Altitude, -attackQtyLimit=AttackQty and true or false, -attackQty=AttackQty, -priority=Priority or 1, -}, -} -return DCSTask -end -function CONTROLLABLE:EnRouteTaskEngageUnit(EngageUnit,Priority,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,Visible,ControllableAttack) -local DCSTask={ -id='EngageUnit', -params={ -unitId=EngageUnit:GetID(), -priority=Priority or 1, -groupAttack=GroupAttack and GroupAttack or false, -visible=Visible and Visible or false, -expend=WeaponExpend or"Auto", -directionEnabled=Direction and true or false, -direction=Direction and math.rad(Direction)or nil, -altitudeEnabled=Altitude and true or false, -altitude=Altitude, -attackQtyLimit=AttackQty and true or false, -attackQty=AttackQty, -controllableAttack=ControllableAttack, -}, -} -return DCSTask -end -function CONTROLLABLE:EnRouteTaskAWACS() -local DCSTask={ -id='AWACS', -params={}, -} -return DCSTask -end -function CONTROLLABLE:EnRouteTaskTanker() -local DCSTask={ -id='Tanker', -params={}, -} -return DCSTask -end -function CONTROLLABLE:EnRouteTaskEWR() -local DCSTask={ -id='EWR', -params={}, -} -return DCSTask -end -function CONTROLLABLE:EnRouteTaskFAC_EngageGroup(AttackGroup,Priority,WeaponType,Designation,Datalink,Frequency,Modulation,CallsignID,CallsignNumber) -local DCSTask={ -id='FAC_EngageGroup', -params={ -groupId=AttackGroup:GetID(), -weaponType=WeaponType or"Auto", -designation=Designation, -datalink=Datalink and Datalink or false, -frequency=(Frequency or 133)*1000000, -modulation=Modulation or radio.modulation.AM, -callname=CallsignID, -number=CallsignNumber, -priority=Priority or 0, -}, -} -return DCSTask -end -function CONTROLLABLE:EnRouteTaskFAC(Frequency,Modulation,CallsignID,CallsignNumber,Priority) -local DCSTask={ -id='FAC', -params={ -frequency=(Frequency or 133)*1000000, -modulation=Modulation or radio.modulation.AM, -callname=CallsignID, -number=CallsignNumber, -priority=Priority or 0 -} -} -return DCSTask -end -function CONTROLLABLE:TaskFunction(FunctionString,...) -local DCSScript={} -DCSScript[#DCSScript+1]="local MissionControllable = GROUP:Find( ... ) " -if arg and arg.n>0 then -local ArgumentKey='_'..tostring(arg):match("table: (.*)") -self:SetState(self,ArgumentKey,arg) -DCSScript[#DCSScript+1]="local Arguments = MissionControllable:GetState( MissionControllable, '"..ArgumentKey.."' ) " -DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable, unpack( Arguments ) )" -else -DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable )" -end -local DCSTask=self:TaskWrappedAction(self:CommandDoScript(table.concat(DCSScript))) -return DCSTask -end -function CONTROLLABLE:TaskMission(TaskMission) -local DCSTask={ -id='Mission', -params={ -TaskMission, -}, -} -return DCSTask -end -do -function CONTROLLABLE:PatrolRoute() -local PatrolGroup=self -if not self:IsInstanceOf("GROUP")then -PatrolGroup=self:GetGroup() -end -self:F({PatrolGroup=PatrolGroup:GetName()}) -if PatrolGroup:IsGround()or PatrolGroup:IsShip()then -local Waypoints=PatrolGroup:GetTemplateRoutePoints() -local FromCoord=PatrolGroup:GetCoordinate() -local depth=0 -local IsSub=false -if PatrolGroup:IsShip()then -local navalvec3=FromCoord:GetVec3() -if navalvec3.y<0 then -depth=navalvec3.y -IsSub=true -end -end -local Waypoint=Waypoints[1] -local Speed=Waypoint.speed or(20/3.6) -local From=FromCoord:WaypointGround(Speed) -if IsSub then -From=FromCoord:WaypointNaval(Speed,Waypoint.alt) -end -table.insert(Waypoints,1,From) -local TaskRoute=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRoute") -self:F({Waypoints=Waypoints}) -local Waypoint=Waypoints[#Waypoints] -PatrolGroup:SetTaskWaypoint(Waypoint,TaskRoute) -PatrolGroup:Route(Waypoints) -end -end -function CONTROLLABLE:PatrolRouteRandom(Speed,Formation,ToWaypoint) -local PatrolGroup=self -if not self:IsInstanceOf("GROUP")then -PatrolGroup=self:GetGroup() -end -self:F({PatrolGroup=PatrolGroup:GetName()}) -if PatrolGroup:IsGround()or PatrolGroup:IsShip()then -local Waypoints=PatrolGroup:GetTemplateRoutePoints() -local FromCoord=PatrolGroup:GetCoordinate() -local FromWaypoint=1 -if ToWaypoint then -FromWaypoint=ToWaypoint -end -local depth=0 -local IsSub=false -if PatrolGroup:IsShip()then -local navalvec3=FromCoord:GetVec3() -if navalvec3.y<0 then -depth=navalvec3.y -IsSub=true -end -end -local ToWaypoint -repeat -ToWaypoint=math.random(1,#Waypoints) -until(ToWaypoint~=FromWaypoint) -self:F({FromWaypoint=FromWaypoint,ToWaypoint=ToWaypoint}) -local Waypoint=Waypoints[ToWaypoint] -local ToCoord=COORDINATE:NewFromVec2({x=Waypoint.x,y=Waypoint.y}) -local Route={} -if IsSub then -Route[#Route+1]=FromCoord:WaypointNaval(Speed,depth) -Route[#Route+1]=ToCoord:WaypointNaval(Speed,Waypoint.alt) -else -Route[#Route+1]=FromCoord:WaypointGround(Speed,Formation) -Route[#Route+1]=ToCoord:WaypointGround(Speed,Formation) -end -local TaskRouteToZone=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRouteRandom",Speed,Formation,ToWaypoint) -PatrolGroup:SetTaskWaypoint(Route[#Route],TaskRouteToZone) -PatrolGroup:Route(Route,1) -end -end -function CONTROLLABLE:PatrolZones(ZoneList,Speed,Formation,DelayMin,DelayMax) -if type(ZoneList)~="table"then -ZoneList={ZoneList} -end -local PatrolGroup=self -if not self:IsInstanceOf("GROUP")then -PatrolGroup=self:GetGroup() -end -DelayMin=DelayMin or 1 -if not DelayMax or DelayMaxLengthDirect*10)or(LengthRoad/LengthOnRoad*100<5)) -self:T(string.format("Length on road = %.3f km",LengthOnRoad/1000)) -self:T(string.format("Length directly = %.3f km",LengthDirect/1000)) -self:T(string.format("Length fraction = %.3f km",LengthOnRoad/LengthDirect)) -self:T(string.format("Length only road = %.3f km",LengthRoad/1000)) -self:T(string.format("Length off road = %.3f km",LengthOffRoad/1000)) -self:T(string.format("Percent on road = %.1f",LengthRoad/LengthOnRoad*100)) -end -local route={} -local canroad=false -if GotPath and LengthRoad and LengthDirect>2000 then -if LongRoad and Shortcut then -table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation)) -table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation)) -else -table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation)) -table.insert(route,PathOnRoad[2]:WaypointGround(Speed,"On Road")) -table.insert(route,PathOnRoad[#PathOnRoad-1]:WaypointGround(Speed,"On Road")) -local dist=ToCoordinate:Get2DDistance(PathOnRoad[#PathOnRoad-1]) -if dist>10 then -table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation)) -table.insert(route,ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5,OffRoadFormation)) -table.insert(route,ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5,OffRoadFormation)) -end -end -canroad=true -else -table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation)) -table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation)) -end -if WaypointFunction then -local N=#route -for n,waypoint in pairs(route)do -waypoint.task={} -waypoint.task.id="ComboTask" -waypoint.task.params={} -waypoint.task.params.tasks={self:TaskFunction("CONTROLLABLE.___PassingWaypoint",n,N,WaypointFunction,unpack(WaypointFunctionArguments or{}))} -end -end -return route,canroad -end -function CONTROLLABLE:TaskGroundOnRailRoads(ToCoordinate,Speed,WaypointFunction,WaypointFunctionArguments) -self:F2({ToCoordinate=ToCoordinate,Speed=Speed}) -Speed=Speed or 20 -local FromCoordinate=self:GetCoordinate() -local PathOnRail,LengthOnRail=FromCoordinate:GetPathOnRoad(ToCoordinate,false,true) -self:T(string.format("Length on railroad = %.3f km",LengthOnRail/1000)) -local route={} -if PathOnRail then -table.insert(route,PathOnRail[1]:WaypointGround(Speed,"On Railroad")) -table.insert(route,PathOnRail[2]:WaypointGround(Speed,"On Railroad")) -end -if WaypointFunction then -local N=#route -for n,waypoint in pairs(route)do -waypoint.task={} -waypoint.task.id="ComboTask" -waypoint.task.params={} -waypoint.task.params.tasks={self:TaskFunction("CONTROLLABLE.___PassingWaypoint",n,N,WaypointFunction,unpack(WaypointFunctionArguments or{}))} -end -end -return route -end -function CONTROLLABLE.___PassingWaypoint(controllable,n,N,waypointfunction,...) -waypointfunction(controllable,n,N,...) -end -function CONTROLLABLE:RouteAirTo(ToCoordinate,AltType,Type,Action,Speed,DelaySeconds) -local FromCoordinate=self:GetCoordinate() -local FromWP=FromCoordinate:WaypointAir() -local ToWP=ToCoordinate:WaypointAir(AltType,Type,Action,Speed) -self:Route({FromWP,ToWP},DelaySeconds) -return self -end -function CONTROLLABLE:TaskRouteToZone(Zone,Randomize,Speed,Formation) -self:F2(Zone) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local ControllablePoint=self:GetVec2() -local PointFrom={} -PointFrom.x=ControllablePoint.x -PointFrom.y=ControllablePoint.y -PointFrom.type="Turning Point" -PointFrom.action=Formation or"Cone" -PointFrom.speed=20/3.6 -local PointTo={} -local ZonePoint -if Randomize then -ZonePoint=Zone:GetRandomVec2() -else -ZonePoint=Zone:GetVec2() -end -PointTo.x=ZonePoint.x -PointTo.y=ZonePoint.y -PointTo.type="Turning Point" -if Formation then -PointTo.action=Formation -else -PointTo.action="Cone" -end -if Speed then -PointTo.speed=Speed -else -PointTo.speed=20/3.6 -end -local Points={PointFrom,PointTo} -self:T3(Points) -self:Route(Points) -return self -end -return nil -end -function CONTROLLABLE:TaskRouteToVec2(Vec2,Speed,Formation) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local ControllablePoint=self:GetVec2() -local PointFrom={} -PointFrom.x=ControllablePoint.x -PointFrom.y=ControllablePoint.y -PointFrom.type="Turning Point" -PointFrom.action=Formation or"Cone" -PointFrom.speed=20/3.6 -local PointTo={} -PointTo.x=Vec2.x -PointTo.y=Vec2.y -PointTo.type="Turning Point" -if Formation then -PointTo.action=Formation -else -PointTo.action="Cone" -end -if Speed then -PointTo.speed=Speed -else -PointTo.speed=20/3.6 -end -local Points={PointFrom,PointTo} -self:T3(Points) -self:Route(Points) -return self -end -return nil -end -end -function CONTROLLABLE:CommandDoScript(DoScript) -local DCSDoScript={ -id="Script", -params={ -command=DoScript, -}, -} -self:T3(DCSDoScript) -return DCSDoScript -end -function CONTROLLABLE:GetTaskMission() -self:F2(self.ControllableName) -return UTILS.DeepCopy(_DATABASE.Templates.Controllables[self.ControllableName].Template) -end -function CONTROLLABLE:GetTaskRoute() -self:F2(self.ControllableName) -return UTILS.DeepCopy(_DATABASE.Templates.Controllables[self.ControllableName].Template.route.points) -end -function CONTROLLABLE:CopyRoute(Begin,End,Randomize,Radius) -self:F2({Begin,End}) -local Points={} -local ControllableName=string.match(self:GetName(),".*#") -if ControllableName then -ControllableName=ControllableName:sub(1,-2) -else -ControllableName=self:GetName() -end -self:T3({ControllableName}) -local Template=_DATABASE.Templates.Controllables[ControllableName].Template -if Template then -if not Begin then -Begin=0 -end -if not End then -End=0 -end -for TPointID=Begin+1,#Template.route.points-End do -if Template.route.points[TPointID]then -Points[#Points+1]=UTILS.DeepCopy(Template.route.points[TPointID]) -if Randomize then -if not Radius then -Radius=500 -end -Points[#Points].x=Points[#Points].x+math.random(Radius*-1,Radius) -Points[#Points].y=Points[#Points].y+math.random(Radius*-1,Radius) -end -end -end -return Points -else -error("Template not found for Controllable : "..ControllableName) -end -return nil -end -function CONTROLLABLE:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -self:F2(self.ControllableName) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local DetectionVisual=(DetectVisual and DetectVisual==true)and Controller.Detection.VISUAL or nil -local DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.OPTICAL or nil -local DetectionRadar=(DetectRadar and DetectRadar==true)and Controller.Detection.RADAR or nil -local DetectionIRST=(DetectIRST and DetectIRST==true)and Controller.Detection.IRST or nil -local DetectionRWR=(DetectRWR and DetectRWR==true)and Controller.Detection.RWR or nil -local DetectionDLINK=(DetectDLINK and DetectDLINK==true)and Controller.Detection.DLINK or nil -local Params={} -if DetectionVisual then -Params[#Params+1]=DetectionVisual -end -if DetectionOptical then -Params[#Params+1]=DetectionOptical -end -if DetectionRadar then -Params[#Params+1]=DetectionRadar -end -if DetectionIRST then -Params[#Params+1]=DetectionIRST -end -if DetectionRWR then -Params[#Params+1]=DetectionRWR -end -if DetectionDLINK then -Params[#Params+1]=DetectionDLINK -end -self:T2({DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK}) -return self:_GetController():getDetectedTargets(Params[1],Params[2],Params[3],Params[4],Params[5],Params[6]) -end -return nil -end -function CONTROLLABLE:IsTargetDetected(DCSObject,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -self:F2(self.ControllableName) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local DetectionVisual=(DetectVisual and DetectVisual==true)and Controller.Detection.VISUAL or nil -local DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.OPTICAL or nil -local DetectionRadar=(DetectRadar and DetectRadar==true)and Controller.Detection.RADAR or nil -local DetectionIRST=(DetectIRST and DetectIRST==true)and Controller.Detection.IRST or nil -local DetectionRWR=(DetectRWR and DetectRWR==true)and Controller.Detection.RWR or nil -local DetectionDLINK=(DetectDLINK and DetectDLINK==true)and Controller.Detection.DLINK or nil -local Controller=self:_GetController() -local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity -=Controller:isTargetDetected(DCSObject,DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK) -return TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity -end -return nil -end -function CONTROLLABLE:IsUnitDetected(Unit,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -self:F2(self.ControllableName) -if Unit and Unit:IsAlive()then -return self:IsTargetDetected(Unit:GetDCSObject(),DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -end -return nil -end -function CONTROLLABLE:IsGroupDetected(Group,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -self:F2(self.ControllableName) -if Group and Group:IsAlive()then -for _,_unit in pairs(Group:GetUnits())do -local unit=_unit -if unit and unit:IsAlive()then -local isdetected=self:IsUnitDetected(unit,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -if isdetected then -return true -end -end -end -return false -end -return nil -end -function CONTROLLABLE:GetDetectedUnitSet(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -local detectedtargets=self:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -local unitset=SET_UNIT:New() -for DetectionObjectID,Detection in pairs(detectedtargets or{})do -local DetectedObject=Detection.object -if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then -local unit=UNIT:Find(DetectedObject) -if unit and unit:IsAlive()then -if not unitset:FindUnit(unit:GetName())then -unitset:AddUnit(unit) -end -end -end -end -return unitset -end -function CONTROLLABLE:GetDetectedGroupSet(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -local detectedtargets=self:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -local groupset=SET_GROUP:New() -for DetectionObjectID,Detection in pairs(detectedtargets or{})do -local DetectedObject=Detection.object -if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then -local unit=UNIT:Find(DetectedObject) -if unit and unit:IsAlive()then -local group=unit:GetGroup() -if group and not groupset:FindGroup(group:GetName())then -groupset:AddGroup(group) -end -end -end -end -return groupset -end -function CONTROLLABLE:SetOption(OptionID,OptionValue) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -Controller:setOption(OptionID,OptionValue) -return self -end -return nil -end -function CONTROLLABLE:OptionROE(ROEvalue) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.ROE,ROEvalue) -elseif self:IsGround()then -Controller:setOption(AI.Option.Ground.id.ROE,ROEvalue) -elseif self:IsShip()then -Controller:setOption(AI.Option.Naval.id.ROE,ROEvalue) -end -return self -end -return nil -end -function CONTROLLABLE:OptionROEHoldFirePossible() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -if self:IsAir()or self:IsGround()or self:IsShip()then -return true -end -return false -end -return nil -end -function CONTROLLABLE:OptionROEHoldFire() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD) -elseif self:IsGround()then -Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.WEAPON_HOLD) -elseif self:IsShip()then -Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.WEAPON_HOLD) -end -return self -end -return nil -end -function CONTROLLABLE:OptionROEReturnFirePossible() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -if self:IsAir()or self:IsGround()or self:IsShip()then -return true -end -return false -end -return nil -end -function CONTROLLABLE:OptionROEReturnFire() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.RETURN_FIRE) -elseif self:IsGround()then -Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.RETURN_FIRE) -elseif self:IsShip()then -Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.RETURN_FIRE) -end -return self -end -return nil -end -function CONTROLLABLE:OptionROEOpenFirePossible() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -if self:IsAir()or self:IsGround()or self:IsShip()then -return true -end -return false -end -return nil -end -function CONTROLLABLE:OptionROEOpenFire() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE) -elseif self:IsGround()then -Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.OPEN_FIRE) -elseif self:IsShip()then -Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.OPEN_FIRE) -end -return self -end -return nil -end -function CONTROLLABLE:OptionROEOpenFireWeaponFreePossible() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -if self:IsAir()then -return true -end -return false -end -return nil -end -function CONTROLLABLE:OptionROEOpenFireWeaponFree() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE_WEAPON_FREE) -end -return self -end -return nil -end -function CONTROLLABLE:OptionROEWeaponFreePossible() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -if self:IsAir()then -return true -end -return false -end -return nil -end -function CONTROLLABLE:OptionROEWeaponFree() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_FREE) -end -return self -end -return nil -end -function CONTROLLABLE:OptionROTNoReactionPossible() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -if self:IsAir()then -return true -end -return false -end -return nil -end -function CONTROLLABLE:OptionROTNoReaction() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION) -end -return self -end -return nil -end -function CONTROLLABLE:OptionROT(ROTvalue) -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,ROTvalue) -end -return self -end -return nil -end -function CONTROLLABLE:OptionROTPassiveDefensePossible() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -if self:IsAir()then -return true -end -return false -end -return nil -end -function CONTROLLABLE:OptionROTPassiveDefense() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE) -end -return self -end -return nil -end -function CONTROLLABLE:OptionROTEvadeFirePossible() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -if self:IsAir()then -return true -end -return false -end -return nil -end -function CONTROLLABLE:OptionROTEvadeFire() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE) -end -return self -end -return nil -end -function CONTROLLABLE:OptionROTVerticalPossible() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -if self:IsAir()then -return true -end -return false -end -return nil -end -function CONTROLLABLE:OptionROTVertical() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE) -end -return self -end -return nil -end -function CONTROLLABLE:OptionAlarmStateAuto() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsGround()then -Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.AUTO) -elseif self:IsShip()then -Controller:setOption(9,0) -end -return self -end -return nil -end -function CONTROLLABLE:OptionAlarmStateGreen() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsGround()then -Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) -elseif self:IsShip()then -Controller:setOption(9,1) -end -return self -end -return nil -end -function CONTROLLABLE:OptionAlarmStateRed() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsGround()then -Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) -elseif self:IsShip()then -Controller:setOption(9,2) -end -return self -end -return nil -end -function CONTROLLABLE:OptionRTBBingoFuel(RTB) -self:F2({self.ControllableName}) -if RTB==nil then -RTB=true -end -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.RTB_ON_BINGO,RTB) -end -return self -end -return nil -end -function CONTROLLABLE:OptionRTBAmmo(WeaponsFlag) -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.RTB_ON_OUT_OF_AMMO,WeaponsFlag) -end -return self -end -return nil -end -function CONTROLLABLE:OptionAllowJettisonWeaponsOnThreat() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.PROHIBIT_JETT,false) -end -return self -end -return nil -end -function CONTROLLABLE:OptionKeepWeaponsOnThreat() -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.PROHIBIT_JETT,true) -end -return self -end -return nil -end -function CONTROLLABLE:OptionProhibitAfterburner(Prohibit) -self:F2({self.ControllableName}) -if Prohibit==nil then -Prohibit=true -end -if self:IsAir()then -self:SetOption(AI.Option.Air.id.PROHIBIT_AB,Prohibit) -end -return self -end -function CONTROLLABLE:OptionECM(ECMvalue) -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if self:IsAir()then -Controller:setOption(AI.Option.Air.id.ECM_USING,ECMvalue or 1) -end -end -return self -end -function CONTROLLABLE:OptionECM_Never() -self:F2({self.ControllableName}) -self:OptionECM(0) -return self -end -function CONTROLLABLE:OptionECM_OnlyLockByRadar() -self:F2({self.ControllableName}) -self:OptionECM(1) -return self -end -function CONTROLLABLE:OptionECM_DetectedLockByRadar() -self:F2({self.ControllableName}) -self:OptionECM(2) -return self -end -function CONTROLLABLE:OptionECM_AlwaysOn() -self:F2({self.ControllableName}) -self:OptionECM(3) -return self -end -function CONTROLLABLE:WayPointInitialize(WayPoints) -self:F({WayPoints}) -if WayPoints then -self.WayPoints=WayPoints -else -self.WayPoints=self:GetTaskRoute() -end -return self -end -function CONTROLLABLE:GetWayPoints() -self:F() -if self.WayPoints then -return self.WayPoints -end -return nil -end -function CONTROLLABLE:WayPointFunction(WayPoint,WayPointIndex,WayPointFunction,...) -self:F2({WayPoint,WayPointIndex,WayPointFunction}) -table.insert(self.WayPoints[WayPoint].task.params.tasks,WayPointIndex) -self.WayPoints[WayPoint].task.params.tasks[WayPointIndex]=self:TaskFunction(WayPointFunction,arg) -return self -end -function CONTROLLABLE:WayPointExecute(WayPoint,WaitTime) -self:F({WayPoint,WaitTime}) -if not WayPoint then -WayPoint=1 -end -for TaskPointID=1,WayPoint-1 do -table.remove(self.WayPoints,1) -end -self:T3(self.WayPoints) -self:SetTask(self:TaskRoute(self.WayPoints),WaitTime) -return self -end -function CONTROLLABLE:IsAirPlane() -self:F2() -local DCSObject=self:GetDCSObject() -if DCSObject then -local Category=DCSObject:getDesc().category -return Category==Unit.Category.AIRPLANE -end -return nil -end -function CONTROLLABLE:IsHelicopter() -self:F2() -local DCSObject=self:GetDCSObject() -if DCSObject then -local Category=DCSObject:getDesc().category -return Category==Unit.Category.HELICOPTER -end -return nil -end -function CONTROLLABLE:OptionRestrictBurner(RestrictBurner) -self:F2({self.ControllableName}) -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if Controller then -if RestrictBurner==true then -if self:IsAir()then -Controller:setOption(16,true) -end -else -if self:IsAir()then -Controller:setOption(16,false) -end -end -end -end -end -function CONTROLLABLE:OptionAAAttackRange(range) -self:F2({self.ControllableName}) -local range=range or 3 -if range<0 or range>4 then -range=3 -end -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if Controller then -if self:IsAir()then -self:SetOption(AI.Option.Air.id.MISSILE_ATTACK,range) -end -end -return self -end -return nil -end -function CONTROLLABLE:OptionEngageRange(EngageRange) -self:F2({self.ControllableName}) -EngageRange=EngageRange or 100 -if EngageRange<0 or EngageRange>100 then -EngageRange=100 -end -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if Controller then -if self:IsGround()then -self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,EngageRange) -end -end -return self -end -return nil -end -function CONTROLLABLE:SetOptionRadarUsing(Option) -self:F2({self.ControllableName}) -if self:IsAir()then -self:SetOption(AI.Option.Air.id.RADAR_USING,Option) -end -return self -end -function CONTROLLABLE:SetOptionRadarUsingNever() -self:F2({self.ControllableName}) -if self:IsAir()then -self:SetOption(AI.Option.Air.id.RADAR_USING,0) -end -return self -end -function CONTROLLABLE:SetOptionRadarUsingForAttackOnly() -self:F2({self.ControllableName}) -if self:IsAir()then -self:SetOption(AI.Option.Air.id.RADAR_USING,1) -end -return self -end -function CONTROLLABLE:SetOptionRadarUsingForSearchIfRequired() -self:F2({self.ControllableName}) -if self:IsAir()then -self:SetOption(AI.Option.Air.id.RADAR_USING,2) -end -return self -end -function CONTROLLABLE:SetOptionRadarUsingForContinousSearch() -self:F2({self.ControllableName}) -if self:IsAir()then -self:SetOption(AI.Option.Air.id.RADAR_USING,3) -end -return self -end -function CONTROLLABLE:RelocateGroundRandomInRadius(speed,radius,onroad,shortcut,formation,onland) -self:F2({self.ControllableName}) -local _coord=self:GetCoordinate() -local _radius=radius or 500 -local _speed=speed or 20 -local _tocoord=_coord:GetRandomCoordinateInRadius(_radius,100) -if onland then -for i=1,50 do -local island=_tocoord:GetSurfaceType()==land.SurfaceType.LAND and true or false -if island then break end -_tocoord=_coord:GetRandomCoordinateInRadius(_radius,100) -end -end -local _onroad=onroad or true -local _grptsk={} -local _candoroad=false -local _shortcut=shortcut or false -local _formation=formation or"Off Road" -if onroad then -_grptsk,_candoroad=self:TaskGroundOnRoad(_tocoord,_speed,_formation,_shortcut) -self:Route(_grptsk,5) -else -self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,_formation) -end -return self -end -function CONTROLLABLE:OptionDisperseOnAttack(Seconds) -self:F2({self.ControllableName}) -local seconds=Seconds or 0 -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if Controller then -if self:IsGround()then -self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK,seconds) -end -end -return self -end -return nil -end -function CONTROLLABLE:IsSubmarine() -self:F2() -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitDescriptor=DCSUnit:getDesc() -if UnitDescriptor.attributes["Submarines"]==true then -return true -else -return false -end -end -return nil -end -function CONTROLLABLE:SetSpeed(Speed,Keep) -self:F2({self.ControllableName}) -local speed=Speed or 5 -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if Controller then -Controller:setSpeed(speed,Keep) -end -end -return self -end -function CONTROLLABLE:SetAltitude(Altitude,Keep,AltType) -self:F2({self.ControllableName}) -local altitude=Altitude or 1000 -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=self:_GetController() -if Controller then -if self:IsAir()then -Controller:setAltitude(altitude,Keep,AltType) -end -end -end -return self -end -function CONTROLLABLE:TaskAerobatics() -local DCSTaskAerobatics={ -id="Aerobatics", -params={ -["maneuversSequency"]={}, -}, -["enabled"]=true, -["auto"]=false, -} -return DCSTaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsCandle(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local CandleTask={ -["name"]="CANDLE", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -} -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],CandleTask) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsEdgeFlight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FlightTime,Side) -local maxrepeats=10 -local maxflight=200 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local flighttime=FlightTime or 10 -if flighttime>200 then maxflight=flighttime end -local EdgeTask={ -["name"]="EDGE_FLIGHT", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["FlightTime"]={ -["max_v"]=maxflight, -["min_v"]=1, -["order"]=6, -["step"]=0.1, -["value"]=flighttime or 10, -}, -["SIDE"]={ -["order"]=7, -["value"]=Side or 0, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],EdgeTask) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsWingoverFlight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FlightTime) -local maxrepeats=10 -local maxflight=200 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local flighttime=FlightTime or 10 -if flighttime>200 then maxflight=flighttime end -local WingoverTask={ -["name"]="WINGOVER_FLIGHT", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["FlightTime"]={ -["max_v"]=maxflight, -["min_v"]=1, -["order"]=6, -["step"]=0.1, -["value"]=flighttime or 10, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],WingoverTask) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsLoop(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local LoopTask={ -["name"]="LOOP", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -} -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],LoopTask) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsHorizontalEight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollDeg) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local LoopTask={ -["name"]="HORIZONTAL_EIGHT", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["SIDE"]={ -["order"]=6, -["value"]=Side or 0, -}, -["ROLL1"]={ -["order"]=7, -["value"]=RollDeg or 60, -}, -["ROLL2"]={ -["order"]=8, -["value"]=RollDeg or 60, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],LoopTask) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsHammerhead(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local Task={ -["name"]="HUMMERHEAD", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["SIDE"]={ -["order"]=6, -["value"]=Side or 0, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsSkewedLoop(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollDeg) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local Task={ -["name"]="SKEWED_LOOP", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["ROLL"]={ -["order"]=6, -["value"]=RollDeg or 60, -}, -["SIDE"]={ -["order"]=7, -["value"]=Side or 0, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsTurn(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollDeg,Pull,Angle) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local Task={ -["name"]="TURN", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["Ny_req"]={ -["order"]=6, -["value"]=Pull or 2, -}, -["ROLL"]={ -["order"]=7, -["value"]=RollDeg or 60, -}, -["SECTOR"]={ -["order"]=8, -["value"]=Angle or 180, -}, -["SIDE"]={ -["order"]=9, -["value"]=Side or 0, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsDive(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Angle,FinalAltitude) -local maxrepeats=10 -local angle=Angle -if angle<15 then angle=15 elseif angle>90 then angle=90 end -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local Task={ -["name"]="DIVE", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 5000, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["Angle"]={ -["max_v"]=90, -["min_v"]=15, -["order"]=6, -["step"]=5, -["value"]=angle or 45, -}, -["FinalAltitude"]={ -["order"]=7, -["value"]=FinalAltitude or 1000, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsMilitaryTurn(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local Task={ -["name"]="MILITARY_TURN", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -} -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsImmelmann(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local Task={ -["name"]="IMMELMAN", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -} -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsStraightFlight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FlightTime) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local maxflight=200 -if Repeats>maxrepeats then maxrepeats=Repeats end -local flighttime=FlightTime or 10 -if flighttime>200 then maxflight=flighttime end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local Task={ -["name"]="STRAIGHT_FLIGHT", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["FlightTime"]={ -["max_v"]=maxflight, -["min_v"]=1, -["order"]=6, -["step"]=0.1, -["value"]=flighttime or 10, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsClimb(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Angle,FinalAltitude) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local Task={ -["name"]="CLIMB", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["Angle"]={ -["max_v"]=90, -["min_v"]=15, -["order"]=6, -["step"]=5, -["value"]=Angle or 45, -}, -["FinalAltitude"]={ -["order"]=7, -["value"]=FinalAltitude or 5000, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsSpiral(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,TurnAngle,Roll,Side,UpDown,Angle) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local updown=UpDown and 1 or 0 -local side=Side and 1 or 0 -local Task={ -["name"]="SPIRAL", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["SECTOR"]={ -["order"]=6, -["value"]=TurnAngle or 360, -}, -["ROLL"]={ -["order"]=7, -["value"]=Roll or 60, -}, -["SIDE"]={ -["order"]=8, -["value"]=side or 0, -}, -["UPDOWN"]={ -["order"]=9, -["value"]=updown or 0, -}, -["Angle"]={ -["max_v"]=90, -["min_v"]=15, -["order"]=10, -["step"]=5, -["value"]=Angle or 45, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsSplitS(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FinalSpeed) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local maxflight=200 -if Repeats>maxrepeats then maxrepeats=Repeats end -local finalspeed=FinalSpeed or 500 -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local Task={ -["name"]="SPLIT_S", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["FinalSpeed"]={ -["order"]=6, -["value"]=finalspeed, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsAileronRoll(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollRate,TurnAngle,FixAngle) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local maxflight=200 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local Task={ -["name"]="AILERON_ROLL", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["SIDE"]={ -["order"]=6, -["value"]=Side or 0, -}, -["RollRate"]={ -["max_v"]=450, -["min_v"]=15, -["order"]=7, -["step"]=5, -["value"]=RollRate or 90, -}, -["SECTOR"]={ -["order"]=8, -["value"]=TurnAngle or 360, -}, -["FIXSECTOR"]={ -["max_v"]=180, -["min_v"]=0, -["order"]=9, -["step"]=5, -["value"]=FixAngle or 0, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsForcedTurn(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,TurnAngle,Side,FlightTime,MinSpeed) -local maxrepeats=10 -local flighttime=FlightTime or 30 -local maxtime=200 -if flighttime>200 then maxtime=flighttime end -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local Task={ -["name"]="FORCED_TURN", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["SECTOR"]={ -["order"]=6, -["value"]=TurnAngle or 360, -}, -["SIDE"]={ -["order"]=7, -["value"]=Side or 0, -}, -["FlightTime"]={ -["max_v"]=maxtime or 200, -["min_v"]=0, -["order"]=8, -["step"]=0.1, -["value"]=flighttime or 30, -}, -["MinSpeed"]={ -["max_v"]=3000, -["min_v"]=30, -["order"]=9, -["step"]=10, -["value"]=MinSpeed or 250, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:TaskAerobaticsBarrelRoll(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollRate,TurnAngle) -local maxrepeats=10 -if Repeats>maxrepeats then maxrepeats=Repeats end -local usesmoke=UseSmoke and 1 or 0 -local startimmediately=StartImmediately and 1 or 0 -local Task={ -["name"]="BARREL_ROLL", -["params"]={ -["RepeatQty"]={ -["max_v"]=maxrepeats, -["min_v"]=1, -["order"]=1, -["value"]=Repeats or 1, -}, -["InitAltitude"]={ -["order"]=2, -["value"]=InitAltitude or 0, -}, -["InitSpeed"]={ -["order"]=3, -["value"]=InitSpeed or 0, -}, -["UseSmoke"]={ -["order"]=4, -["value"]=usesmoke, -}, -["StartImmediatly"]={ -["order"]=5, -["value"]=startimmediately, -}, -["SIDE"]={ -["order"]=6, -["value"]=Side or 0, -}, -["RollRate"]={ -["max_v"]=450, -["min_v"]=15, -["order"]=7, -["step"]=5, -["value"]=RollRate or 90, -}, -["SECTOR"]={ -["order"]=8, -["value"]=TurnAngle or 360, -}, -} -} -table.insert(TaskAerobatics.params["maneuversSequency"],Task) -return TaskAerobatics -end -function CONTROLLABLE:PatrolRaceTrack(Point1,Point2,Altitude,Speed,Formation,AGL,Delay) -local PatrolGroup=self -if not self:IsInstanceOf("GROUP")then -PatrolGroup=self:GetGroup() -end -local delay=Delay or 1 -self:F({PatrolGroup=PatrolGroup:GetName()}) -if PatrolGroup:IsAir()then -if Formation then -PatrolGroup:SetOption(AI.Option.Air.id.FORMATION,Formation) -end -local FromCoord=PatrolGroup:GetCoordinate() -local ToCoord=Point1:GetCoordinate() -if Altitude then -local asl=true -if AGL then asl=false end -FromCoord:SetAltitude(Altitude,asl) -ToCoord:SetAltitude(Altitude,asl) -end -local Route={} -Route[#Route+1]=FromCoord:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,description,timeReFuAr) -Route[#Route+1]=ToCoord:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,description,timeReFuAr) -local TaskRouteToZone=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRaceTrack",Point2,Point1,Altitude,Speed,Formation,Delay) -PatrolGroup:SetTaskWaypoint(Route[#Route],TaskRouteToZone) -PatrolGroup:Route(Route,Delay) -end -return self -end -GROUP={ -ClassName="GROUP", -} -GROUP.Takeoff={ -Air=1, -Runway=2, -Hot=3, -Cold=4, -} -GROUPTEMPLATE={} -GROUPTEMPLATE.Takeoff={ -[GROUP.Takeoff.Air]={"Turning Point","Turning Point"}, -[GROUP.Takeoff.Runway]={"TakeOff","From Runway"}, -[GROUP.Takeoff.Hot]={"TakeOffParkingHot","From Parking Area Hot"}, -[GROUP.Takeoff.Cold]={"TakeOffParking","From Parking Area"} -} -GROUP.Attribute={ -AIR_TRANSPORTPLANE="Air_TransportPlane", -AIR_AWACS="Air_AWACS", -AIR_FIGHTER="Air_Fighter", -AIR_BOMBER="Air_Bomber", -AIR_TANKER="Air_Tanker", -AIR_TRANSPORTHELO="Air_TransportHelo", -AIR_ATTACKHELO="Air_AttackHelo", -AIR_UAV="Air_UAV", -AIR_OTHER="Air_OtherAir", -GROUND_APC="Ground_APC", -GROUND_TRUCK="Ground_Truck", -GROUND_INFANTRY="Ground_Infantry", -GROUND_IFV="Ground_IFV", -GROUND_ARTILLERY="Ground_Artillery", -GROUND_TANK="Ground_Tank", -GROUND_TRAIN="Ground_Train", -GROUND_EWR="Ground_EWR", -GROUND_AAA="Ground_AAA", -GROUND_SAM="Ground_SAM", -GROUND_OTHER="Ground_OtherGround", -NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier", -NAVAL_WARSHIP="Naval_WarShip", -NAVAL_ARMEDSHIP="Naval_ArmedShip", -NAVAL_UNARMEDSHIP="Naval_UnarmedShip", -NAVAL_OTHER="Naval_OtherNaval", -OTHER_UNKNOWN="Other_Unknown", -} -function GROUP:NewTemplate(GroupTemplate,CoalitionSide,CategoryID,CountryID) -local GroupName=GroupTemplate.name -_DATABASE:_RegisterGroupTemplate(GroupTemplate,CoalitionSide,CategoryID,CountryID,GroupName) -local self=BASE:Inherit(self,CONTROLLABLE:New(GroupName)) -self.GroupName=GroupName -if not _DATABASE.GROUPS[GroupName]then -_DATABASE.GROUPS[GroupName]=self -end -self:SetEventPriority(4) -return self -end -function GROUP:Register(GroupName) -local self=BASE:Inherit(self,CONTROLLABLE:New(GroupName)) -self.GroupName=GroupName -self:SetEventPriority(4) -return self -end -function GROUP:Find(DCSGroup) -local GroupName=DCSGroup:getName() -local GroupFound=_DATABASE:FindGroup(GroupName) -return GroupFound -end -function GROUP:FindByName(GroupName) -local GroupFound=_DATABASE:FindGroup(GroupName) -return GroupFound -end -function GROUP:FindByMatching(Pattern) -local GroupFound=nil -for name,group in pairs(_DATABASE.GROUPS)do -if string.match(name,Pattern)then -GroupFound=group -break -end -end -return GroupFound -end -function GROUP:FindAllByMatching(Pattern) -local GroupsFound={} -for name,group in pairs(_DATABASE.GROUPS)do -if string.match(name,Pattern)then -GroupsFound[#GroupsFound+1]=group -end -end -return GroupsFound -end -function GROUP:GetDCSObject() -local DCSGroup=Group.getByName(self.GroupName) -if DCSGroup then -return DCSGroup -end -return nil -end -function GROUP:GetPositionVec3() -self:F2(self.PositionableName) -local DCSPositionable=self:GetDCSObject() -if DCSPositionable then -local unit=DCSPositionable:getUnits()[1] -if unit then -local PositionablePosition=unit:getPosition().p -self:T3(PositionablePosition) -return PositionablePosition -end -end -return nil -end -function GROUP:IsAlive() -self:F2(self.GroupName) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -if DCSGroup:isExist()then -local DCSUnit=DCSGroup:getUnit(1) -if DCSUnit then -local GroupIsAlive=DCSUnit:isActive() -self:T3(GroupIsAlive) -return GroupIsAlive -end -end -end -return nil -end -function GROUP:IsActive() -self:F2(self.GroupName) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -local unit=DCSGroup:getUnit(1) -if unit then -local GroupIsActive=unit:isActive() -return GroupIsActive -end -end -return nil -end -function GROUP:Destroy(GenerateEvent,delay) -self:F2(self.GroupName) -if delay and delay>0 then -self:ScheduleOnce(delay,GROUP.Destroy,self,GenerateEvent) -else -local DCSGroup=self:GetDCSObject() -if DCSGroup then -for Index,UnitData in pairs(DCSGroup:getUnits())do -if GenerateEvent and GenerateEvent==true then -if self:IsAir()then -self:CreateEventCrash(timer.getTime(),UnitData) -else -self:CreateEventDead(timer.getTime(),UnitData) -end -elseif GenerateEvent==false then -else -self:CreateEventRemoveUnit(timer.getTime(),UnitData) -end -end -USERFLAG:New(self:GetName()):Set(100) -DCSGroup:destroy() -DCSGroup=nil -end -end -return nil -end -function GROUP:GetCategory() -self:F2(self.GroupName) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -local GroupCategory=DCSGroup:getCategory() -self:T3(GroupCategory) -return GroupCategory -end -return nil -end -function GROUP:GetCategoryName() -self:F2(self.GroupName) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -local CategoryNames={ -[Group.Category.AIRPLANE]="Airplane", -[Group.Category.HELICOPTER]="Helicopter", -[Group.Category.GROUND]="Ground Unit", -[Group.Category.SHIP]="Ship", -[Group.Category.TRAIN]="Train", -} -local GroupCategory=DCSGroup:getCategory() -self:T3(GroupCategory) -return CategoryNames[GroupCategory] -end -return nil -end -function GROUP:GetCoalition() -self:F2(self.GroupName) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -local GroupCoalition=DCSGroup:getCoalition() -self:T3(GroupCoalition) -return GroupCoalition -end -return nil -end -function GROUP:GetCountry() -self:F2(self.GroupName) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -local GroupCountry=DCSGroup:getUnit(1):getCountry() -self:T3(GroupCountry) -return GroupCountry -end -return nil -end -function GROUP:HasAttribute(attribute,all) -local _units=self:GetUnits() -if _units then -local _allhave=true -local _onehas=false -for _,_unit in pairs(_units)do -local _unit=_unit -if _unit then -local _hastit=_unit:HasAttribute(attribute) -if _hastit==true then -_onehas=true -else -_allhave=false -end -end -end -if all==true then -return _allhave -else -return _onehas -end -end -return nil -end -function GROUP:GetSpeedMax() -self:F2(self.GroupName) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -local Units=self:GetUnits() -local speedmax=nil -for _,unit in pairs(Units)do -local unit=unit -local speed=unit:GetSpeedMax() -if speedmax==nil or speed0 then -self:ScheduleOnce(delay,GROUP.Activate,self) -else -trigger.action.activateGroup(self:GetDCSObject()) -end -return self -end -function GROUP:GetTypeName() -self:F2(self.GroupName) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() -self:T3(GroupTypeName) -return(GroupTypeName) -end -return nil -end -function GROUP:GetNatoReportingName() -self:F2(self.GroupName) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() -self:T3(GroupTypeName) -return UTILS.GetReportingName(GroupTypeName) -end -return"Bogey" -end -function GROUP:GetPlayerName() -self:F2(self.GroupName) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -local PlayerName=DCSGroup:getUnit(1):getPlayerName() -self:T3(PlayerName) -return(PlayerName) -end -return nil -end -function GROUP:GetCallsign() -self:F2(self.GroupName) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -local GroupCallSign=DCSGroup:getUnit(1):getCallsign() -self:T3(GroupCallSign) -return GroupCallSign -end -BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()}) -return nil -end -function GROUP:GetVec2() -local Unit=self:GetUnit(1) -if Unit then -local vec2=Unit:GetVec2() -return vec2 -end -end -function GROUP:GetVec3() -local unit=self:GetUnit(1) -if unit then -local vec3=unit:GetVec3() -return vec3 -end -self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) -return nil -end -function GROUP:GetAverageVec3() -local units=self:GetUnits()or{} -local x=0;local y=0;local z=0;local n=0 -for _,unit in pairs(units)do -local vec3=nil -if unit and unit:IsAlive()then -vec3=unit:GetVec3() -end -if vec3 then -x=x+vec3.x -y=y+vec3.y -z=z+vec3.z -n=n+1 -end -end -if n>0 then -local Vec3={x=x/n,y=y/n,z=z/n} -return Vec3 -else -return self:GetVec3() -end -end -function GROUP:GetPointVec2() -self:F2(self.GroupName) -local FirstUnit=self:GetUnit(1) -if FirstUnit then -local FirstUnitPointVec2=FirstUnit:GetPointVec2() -self:T3(FirstUnitPointVec2) -return FirstUnitPointVec2 -end -BASE:E({"Cannot GetPointVec2",Group=self,Alive=self:IsAlive()}) -return nil -end -function GROUP:GetAverageCoordinate() -local vec3=self:GetAverageVec3() -if vec3 then -local coord=COORDINATE:NewFromVec3(vec3) -local Heading=self:GetHeading() -coord.Heading=Heading -return coord -else -local coord=self:GetCoordinate() -if coord then -return coord -else -BASE:E({"Cannot GetAverageCoordinate",Group=self,Alive=self:IsAlive()}) -return nil -end -end -end -function GROUP:GetCoordinate() -local Units=self:GetUnits()or{} -for _,_unit in pairs(Units)do -local FirstUnit=_unit -if FirstUnit then -local FirstUnitCoordinate=FirstUnit:GetCoordinate() -if FirstUnitCoordinate then -local Heading=self:GetHeading() -FirstUnitCoordinate.Heading=Heading -return FirstUnitCoordinate -end -end -end -BASE:E({"Cannot GetCoordinate",Group=self,Alive=self:IsAlive()}) -end -function GROUP:GetRandomVec3(Radius) -self:F2(self.GroupName) -local FirstUnit=self:GetUnit(1) -if FirstUnit then -local FirstUnitRandomPointVec3=FirstUnit:GetRandomVec3(Radius) -self:T3(FirstUnitRandomPointVec3) -return FirstUnitRandomPointVec3 -end -BASE:E({"Cannot GetRandomVec3",Group=self,Alive=self:IsAlive()}) -return nil -end -function GROUP:GetHeading() -self:F2(self.GroupName) -self:F2(self.GroupName) -local GroupSize=self:GetSize() -local HeadingAccumulator=0 -local n=0 -local Units=self:GetUnits() -if GroupSize then -for _,unit in pairs(Units)do -if unit and unit:IsAlive()then -HeadingAccumulator=HeadingAccumulator+unit:GetHeading() -n=n+1 -end -end -return math.floor(HeadingAccumulator/n) -end -BASE:E({"Cannot GetHeading",Group=self,Alive=self:IsAlive()}) -return nil -end -function GROUP:GetFuelMin() -self:F3(self.ControllableName) -if not self:GetDCSObject()then -BASE:E({"Cannot GetFuel",Group=self,Alive=self:IsAlive()}) -return 0 -end -local min=65535 -local unit=nil -local tmp=nil -for UnitID,UnitData in pairs(self:GetUnits())do -if UnitData and UnitData:IsAlive()then -tmp=UnitData:GetFuel() -if tmpGroupVelocityMax then -GroupVelocityMax=UnitVelocity -end -end -return GroupVelocityMax -end -return nil -end -function GROUP:GetMinHeight() -self:F2() -local DCSGroup=self:GetDCSObject() -if DCSGroup then -local GroupHeightMin=999999999 -for Index,UnitData in pairs(DCSGroup:getUnits())do -local UnitData=UnitData -local UnitHeight=UnitData:getPoint() -if UnitHeightGroupHeightMax then -GroupHeightMax=UnitHeight -end -end -return GroupHeightMax -end -return nil -end -function GROUP:GetTemplate() -local GroupName=self:GetName() -return UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName)) -end -function GROUP:GetTemplateRoutePoints() -local GroupName=self:GetName() -return UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName).route.points) -end -function GROUP:SetTemplateControlled(Template,Controlled) -Template.uncontrolled=not Controlled -return Template -end -function GROUP:SetTemplateCountry(Template,CountryID) -Template.CountryID=CountryID -return Template -end -function GROUP:SetTemplateCoalition(Template,CoalitionID) -Template.CoalitionID=CoalitionID -return Template -end -function GROUP:InitHeading(Heading) -self.InitRespawnHeading=Heading -return self -end -function GROUP:InitHeight(Height) -self.InitRespawnHeight=Height -return self -end -function GROUP:InitZone(Zone) -self.InitRespawnZone=Zone -return self -end -function GROUP:InitRandomizePositionZone(PositionZone) -self.InitRespawnRandomizePositionZone=PositionZone -self.InitRespawnRandomizePositionInner=nil -self.InitRespawnRandomizePositionOuter=nil -return self -end -function GROUP:InitRandomizePositionRadius(OuterRadius,InnerRadius) -self.InitRespawnRandomizePositionZone=nil -self.InitRespawnRandomizePositionOuter=OuterRadius -self.InitRespawnRandomizePositionInner=InnerRadius -return self -end -function GROUP:InitCoordinate(coordinate) -self:F({coordinate=coordinate}) -self.InitCoord=coordinate -return self -end -function GROUP:InitRadioCommsOnOff(switch) -self:F({switch=switch}) -if switch==true or switch==nil then -self.InitRespawnRadio=true -else -self.InitRespawnRadio=false -end -return self -end -function GROUP:InitRadioFrequency(frequency) -self:F({frequency=frequency}) -self.InitRespawnFreq=frequency -return self -end -function GROUP:InitRadioModulation(modulation) -self:F({modulation=modulation}) -if modulation and modulation:lower()=="fm"then -self.InitRespawnModu=radio.modulation.FM -else -self.InitRespawnModu=radio.modulation.AM -end -return self -end -function GROUP:InitModex(modex) -self:F({modex=modex}) -if modex then -self.InitRespawnModex=tonumber(modex) -end -return self -end -function GROUP:Respawn(Template,Reset) -Template=Template or self:GetTemplate() -local function _Heading(course) -local h -if course<=180 then -h=math.rad(course) -else -h=-math.rad(360-course) -end -return h -end -if self:IsAlive()then -local Zone=self.InitRespawnZone -local Vec3=Zone and Zone:GetVec3()or self:GetVec3() -local From={x=Template.x,y=Template.y} -Template.x=Vec3.x -Template.y=Vec3.z -self:F(#Template.units) -if Reset==true then -for UnitID,UnitData in pairs(self:GetUnits())do -local GroupUnit=UnitData -self:F(GroupUnit:GetName()) -if GroupUnit:IsAlive()then -self:I("FF Alive") -local GroupUnitVec3=GroupUnit:GetVec3() -if Zone then -if self.InitRespawnRandomizePositionZone then -GroupUnitVec3=Zone:GetRandomVec3() -else -if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then -GroupUnitVec3=POINT_VEC3:NewFromVec2(From):GetRandomPointVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner) -else -GroupUnitVec3=Zone:GetVec3() -end -end -end -if self.InitCoord then -GroupUnitVec3=self.InitCoord:GetVec3() -end -Template.units[UnitID].alt=self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y -if Zone then -Template.units[UnitID].x=(Template.units[UnitID].x-From.x)+GroupUnitVec3.x -Template.units[UnitID].y=(Template.units[UnitID].y-From.y)+GroupUnitVec3.z -else -Template.units[UnitID].x=GroupUnitVec3.x -Template.units[UnitID].y=GroupUnitVec3.z -end -Template.units[UnitID].heading=_Heading(self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading()) -Template.units[UnitID].psi=-Template.units[UnitID].heading -self:F({UnitID,Template.units[UnitID],Template.units[UnitID]}) -end -end -elseif Reset==false then -for UnitID,TemplateUnitData in pairs(Template.units)do -self:F("Reset") -local GroupUnitVec3={x=TemplateUnitData.x,y=TemplateUnitData.alt,z=TemplateUnitData.y} -if Zone then -if self.InitRespawnRandomizePositionZone then -GroupUnitVec3=Zone:GetRandomVec3() -else -if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then -GroupUnitVec3=POINT_VEC3:NewFromVec2(From):GetRandomPointVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner) -else -GroupUnitVec3=Zone:GetVec3() -end -end -end -if self.InitCoord then -GroupUnitVec3=self.InitCoord:GetVec3() -end -Template.units[UnitID].alt=self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y -Template.units[UnitID].x=(Template.units[UnitID].x-From.x)+GroupUnitVec3.x -Template.units[UnitID].y=(Template.units[UnitID].y-From.y)+GroupUnitVec3.z -Template.units[UnitID].heading=self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading -self:F({UnitID,Template.units[UnitID],Template.units[UnitID]}) -end -else -local units=self:GetUnits() -for UnitID,Unit in pairs(Template.units)do -for _,_unit in pairs(units)do -local unit=_unit -if unit:GetName()==Unit.name then -local coord=unit:GetCoordinate() -local heading=unit:GetHeading() -Unit.x=coord.x -Unit.y=coord.z -Unit.alt=coord.y -Unit.heading=math.rad(heading) -Unit.psi=-Unit.heading -end -end -end -end -end -if self.InitRespawnModex then -for UnitID=1,#Template.units do -Template.units[UnitID].onboard_num=string.format("%03d",self.InitRespawnModex+(UnitID-1)) -end -end -if self.InitRespawnRadio then -Template.communication=self.InitRespawnRadio -end -if self.InitRespawnFreq then -Template.frequency=self.InitRespawnFreq -end -if self.InitRespawnModu then -Template.modulation=self.InitRespawnModu -end -self:Destroy(false) -self:T({Template=Template}) -_DATABASE:Spawn(Template) -self:ResetEvents() -return self -end -function GROUP:RespawnAtCurrentAirbase(SpawnTemplate,Takeoff,Uncontrolled) -self:F2({SpawnTemplate,Takeoff,Uncontrolled}) -if self and self:IsAlive()then -local airbase=self:GetCoordinate():GetClosestAirbase() -if airbase then -self:F2("Closest airbase = "..airbase:GetName()) -else -self:E("ERROR: could not find closest airbase!") -return nil -end -Takeoff=Takeoff or SPAWN.Takeoff.Hot -local AirbaseCoord=airbase:GetCoordinate() -SpawnTemplate=SpawnTemplate or self:GetTemplate() -if SpawnTemplate then -local SpawnPoint=SpawnTemplate.route.points[1] -SpawnPoint.linkUnit=nil -SpawnPoint.helipadId=nil -SpawnPoint.airdromeId=nil -local AirbaseID=airbase:GetID() -local AirbaseCategory=airbase:GetAirbaseCategory() -if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then -SpawnPoint.linkUnit=AirbaseID -SpawnPoint.helipadId=AirbaseID -elseif AirbaseCategory==Airbase.Category.AIRDROME then -SpawnPoint.airdromeId=AirbaseID -end -SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1] -SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2] -local units=self:GetUnits() -local x -local y -for UnitID=1,#units do -local unit=units[UnitID] -local Parkingspot,TermialID,Distance=unit:GetCoordinate():GetClosestParkingSpot(airbase) -self:T2(string.format("Closest parking spot distance = %s, terminal ID=%s",tostring(Distance),tostring(TermialID))) -local uc=unit:GetCoordinate() -SpawnTemplate.units[UnitID].x=uc.x -SpawnTemplate.units[UnitID].y=uc.z -SpawnTemplate.units[UnitID].alt=uc.y -SpawnTemplate.units[UnitID].parking=TermialID -SpawnTemplate.units[UnitID].parking_id=nil -end -SpawnPoint.x=SpawnTemplate.units[1].x -SpawnPoint.y=SpawnTemplate.units[1].y -SpawnPoint.alt=SpawnTemplate.units[1].alt -SpawnTemplate.x=SpawnTemplate.units[1].x -SpawnTemplate.y=SpawnTemplate.units[1].y -SpawnTemplate.uncontrolled=Uncontrolled -if self.InitRespawnRadio then -SpawnTemplate.communication=self.InitRespawnRadio -end -if self.InitRespawnFreq then -SpawnTemplate.frequency=self.InitRespawnFreq -end -if self.InitRespawnModu then -SpawnTemplate.modulation=self.InitRespawnModu -end -self:Destroy(false) -_DATABASE:Spawn(SpawnTemplate) -self:ResetEvents() -return self -end -else -self:E("WARNING: GROUP is not alive!") -end -return nil -end -function GROUP:GetTaskMission() -self:F2(self.GroupName) -return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template) -end -function GROUP:GetTaskRoute() -self:F2(self.GroupName) -return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points) -end -function GROUP:CopyRoute(Begin,End,Randomize,Radius) -self:F2({Begin,End}) -local Points={} -local GroupName=string.match(self:GetName(),".*#") -if GroupName then -GroupName=GroupName:sub(1,-2) -else -GroupName=self:GetName() -end -self:T3({GroupName}) -local Template=_DATABASE.Templates.Groups[GroupName].Template -if Template then -if not Begin then -Begin=0 -end -if not End then -End=0 -end -for TPointID=Begin+1,#Template.route.points-End do -if Template.route.points[TPointID]then -Points[#Points+1]=UTILS.DeepCopy(Template.route.points[TPointID]) -if Randomize then -if not Radius then -Radius=500 -end -Points[#Points].x=Points[#Points].x+math.random(Radius*-1,Radius) -Points[#Points].y=Points[#Points].y+math.random(Radius*-1,Radius) -end -end -end -return Points -else -error("Template not found for Group : "..GroupName) -end -return nil -end -function GROUP:CalculateThreatLevelA2G() -local MaxThreatLevelA2G=0 -for UnitName,UnitData in pairs(self:GetUnits())do -local ThreatUnit=UnitData -local ThreatLevelA2G=ThreatUnit:GetThreatLevel() -if ThreatLevelA2G>MaxThreatLevelA2G then -MaxThreatLevelA2G=ThreatLevelA2G -end -end -self:T3(MaxThreatLevelA2G) -return MaxThreatLevelA2G -end -function GROUP:GetThreatLevel() -local threatlevelMax=0 -for UnitName,UnitData in pairs(self:GetUnits())do -local ThreatUnit=UnitData -local threatlevel=ThreatUnit:GetThreatLevel() -if threatlevel>threatlevelMax then -threatlevelMax=threatlevel -end -end -return threatlevelMax -end -function GROUP:InAir() -self:F2(self.GroupName) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -local DCSUnit=DCSGroup:getUnit(1) -if DCSUnit then -local GroupInAir=DCSGroup:getUnit(1):inAir() -self:T3(GroupInAir) -return GroupInAir -end -end -return nil -end -function GROUP:IsAirborne(AllUnits) -self:F2(self.GroupName) -local units=self:GetUnits() -if units then -if AllUnits then -for _,_unit in pairs(units)do -local unit=_unit -if unit then -local inair=unit:InAir() -if not inair then -return false -end -end -end -return true -else -for _,_unit in pairs(units)do -local unit=_unit -if unit then -local inair=unit:InAir() -if inair then -return true -end -end -return false -end -end -end -return nil -end -function GROUP:GetDCSDesc(n) -n=n or 1 -local unit=self:GetUnit(n) -if unit and unit:IsAlive()~=nil then -local desc=unit:GetDesc() -return desc -end -return nil -end -function GROUP:GetAttribute() -local attribute=GROUP.Attribute.OTHER_UNKNOWN -if self then -local transportplane=self:HasAttribute("Transports")and self:HasAttribute("Planes") -local awacs=self:HasAttribute("AWACS") -local fighter=self:HasAttribute("Fighters")or self:HasAttribute("Interceptors")or self:HasAttribute("Multirole fighters")or(self:HasAttribute("Bombers")and not self:HasAttribute("Strategic bombers")) -local bomber=self:HasAttribute("Strategic bombers") -local tanker=self:HasAttribute("Tankers") -local uav=self:HasAttribute("UAVs") -local transporthelo=self:HasAttribute("Transport helicopters") -local attackhelicopter=self:HasAttribute("Attack helicopters") -local apc=self:HasAttribute("APC") -local truck=self:HasAttribute("Trucks")and self:GetCategory()==Group.Category.GROUND -local infantry=self:HasAttribute("Infantry") -local artillery=self:HasAttribute("Artillery") -local tank=self:HasAttribute("Old Tanks")or self:HasAttribute("Modern Tanks")or self:HasAttribute("Tanks") -local aaa=self:HasAttribute("AAA")and(not self:HasAttribute("SAM elements")) -local ewr=self:HasAttribute("EWR") -local ifv=self:HasAttribute("IFV") -local sam=self:HasAttribute("SAM elements")or self:HasAttribute("Optical Tracker") -local train=self:GetCategory()==Group.Category.TRAIN -local aircraftcarrier=self:HasAttribute("Aircraft Carriers") -local warship=self:HasAttribute("Heavy armed ships") -local armedship=self:HasAttribute("Armed ships") -local unarmedship=self:HasAttribute("Unarmed ships") -if fighter then -attribute=GROUP.Attribute.AIR_FIGHTER -elseif bomber then -attribute=GROUP.Attribute.AIR_BOMBER -elseif awacs then -attribute=GROUP.Attribute.AIR_AWACS -elseif transportplane then -attribute=GROUP.Attribute.AIR_TRANSPORTPLANE -elseif tanker then -attribute=GROUP.Attribute.AIR_TANKER -elseif attackhelicopter then -attribute=GROUP.Attribute.AIR_ATTACKHELO -elseif transporthelo then -attribute=GROUP.Attribute.AIR_TRANSPORTHELO -elseif uav then -attribute=GROUP.Attribute.AIR_UAV -elseif ewr then -attribute=GROUP.Attribute.GROUND_EWR -elseif sam then -attribute=GROUP.Attribute.GROUND_SAM -elseif aaa then -attribute=GROUP.Attribute.GROUND_AAA -elseif artillery then -attribute=GROUP.Attribute.GROUND_ARTILLERY -elseif tank then -attribute=GROUP.Attribute.GROUND_TANK -elseif ifv then -attribute=GROUP.Attribute.GROUND_IFV -elseif apc then -attribute=GROUP.Attribute.GROUND_APC -elseif infantry then -attribute=GROUP.Attribute.GROUND_INFANTRY -elseif truck then -attribute=GROUP.Attribute.GROUND_TRUCK -elseif train then -attribute=GROUP.Attribute.GROUND_TRAIN -elseif aircraftcarrier then -attribute=GROUP.Attribute.NAVAL_AIRCRAFTCARRIER -elseif warship then -attribute=GROUP.Attribute.NAVAL_WARSHIP -elseif armedship then -attribute=GROUP.Attribute.NAVAL_ARMEDSHIP -elseif unarmedship then -attribute=GROUP.Attribute.NAVAL_UNARMEDSHIP -else -if self:IsGround()then -attribute=GROUP.Attribute.GROUND_OTHER -elseif self:IsShip()then -attribute=GROUP.Attribute.NAVAL_OTHER -elseif self:IsAir()then -attribute=GROUP.Attribute.AIR_OTHER -else -attribute=GROUP.Attribute.OTHER_UNKNOWN -end -end -end -return attribute -end -do -function GROUP:RouteRTB(RTBAirbase,Speed) -self:F({RTBAirbase:GetName(),Speed}) -local DCSGroup=self:GetDCSObject() -if DCSGroup then -if RTBAirbase then -local Speed=Speed or self:GetSpeedMax()*0.8 -local coord=self:GetCoordinate() -local PointFrom=coord:WaypointAirTurningPoint(nil,Speed) -local PointLanding=RTBAirbase:GetCoordinate():WaypointAirLanding(Speed,RTBAirbase) -local Points={PointFrom,PointLanding} -self:T3(Points) -local Template=self:GetTemplate() -Template.route.points=Points -self:Respawn(Template,true) -self:Route(Points) -else -self:ClearTasks() -end -end -return self -end -end -function GROUP:OnReSpawn(ReSpawnFunction) -self.ReSpawnFunction=ReSpawnFunction -end -do -function GROUP:HandleEvent(Event,EventFunction,...) -self:EventDispatcher():OnEventForGroup(self:GetName(),EventFunction,self,Event,...) -return self -end -function GROUP:UnHandleEvent(Event) -self:EventDispatcher():RemoveEvent(self,Event) -return self -end -function GROUP:ResetEvents() -self:EventDispatcher():Reset(self) -for UnitID,UnitData in pairs(self:GetUnits())do -UnitData:ResetEvents() -end -return self -end -end -do -function GROUP:GetPlayerNames() -local HasPlayers=false -local PlayerNames={} -local Units=self:GetUnits() -for UnitID,UnitData in pairs(Units)do -local Unit=UnitData -local PlayerName=Unit:GetPlayerName() -if PlayerName and PlayerName~=""then -PlayerNames=PlayerNames or{} -table.insert(PlayerNames,PlayerName) -HasPlayers=true -end -end -if HasPlayers==true then -self:F2(PlayerNames) -return PlayerNames -end -return nil -end -function GROUP:GetPlayerCount() -local PlayerCount=0 -local Units=self:GetUnits() -for UnitID,UnitData in pairs(Units or{})do -local Unit=UnitData -local PlayerName=Unit:GetPlayerName() -if PlayerName and PlayerName~=""then -PlayerCount=PlayerCount+1 -end -end -return PlayerCount -end -end -function GROUP:EnableEmission(switch) -self:F2(self.GroupName) -local switch=switch or false -local DCSUnit=self:GetDCSObject() -if DCSUnit then -DCSUnit:enableEmission(switch) -end -return self -end -function GROUP:SetCommandInvisible(switch) -self:F2(self.GroupName) -if switch==nil then -switch=false -end -local SetInvisible={id='SetInvisible',params={value=switch}} -self:SetCommand(SetInvisible) -return self -end -function GROUP:SetCommandImmortal(switch) -self:F2(self.GroupName) -if switch==nil then -switch=false -end -local SetImmortal={id='SetImmortal',params={value=switch}} -self:SetCommand(SetImmortal) -return self -end -function GROUP:GetSkill() -self:F2(self.GroupName) -local unit=self:GetUnit(1) -local name=unit:GetName() -local skill=_DATABASE.Templates.Units[name].Template.skill or"Random" -return skill -end -function GROUP:GetHighestThreat() -local units=self:GetUnits() -if units then -local threat=nil;local maxtl=0 -for _,_unit in pairs(units or{})do -local unit=_unit -if unit and unit:IsAlive()then -local tl=unit:GetThreatLevel() -if tl>maxtl then -maxtl=tl -threat=unit -end -end -end -return threat,maxtl -end -return nil,nil -end -function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) -local callsign="Ghost 1" -if self:IsAlive()then -local IsPlayer=self:IsPlayer() -local shortcallsign=self:GetCallsign()or"unknown91" -local callsignroot=string.match(shortcallsign,'(%a+)')or"Ghost" -local groupname=self:GetName() -local callnumber=string.match(shortcallsign,"(%d+)$")or"91" -local callnumbermajor=string.char(string.byte(callnumber,1)) -local callnumberminor=string.char(string.byte(callnumber,2)) -local personalized=false -if CallsignTranslations and CallsignTranslations[callsignroot]then -callsignroot=CallsignTranslations[callsignroot] -elseif IsPlayer and string.find(groupname,"#")then -if Keepnumber then -shortcallsign=string.match(groupname,"#(.+)")or"Ghost 111" -else -shortcallsign=string.match(groupname,"#%s*([%a]+)")or"Ghost" -end -personalized=true -elseif IsPlayer and string.find(self:GetPlayerName(),"|")then -shortcallsign=string.match(self:GetPlayerName(),"|%s*([%a]+)")or string.match(self:GetPlayerName(),"|%s*([%d]+)")or"Ghost" -personalized=true -end -if personalized then -shortcallsign=string.gsub(shortcallsign,"^%s*","") -shortcallsign=string.gsub(shortcallsign,"%s*$","") -if Keepnumber then -return shortcallsign -elseif ShortCallsign then -callsign=shortcallsign.." "..callnumbermajor -else -callsign=shortcallsign.." "..callnumbermajor.." "..callnumberminor -end -return callsign -end -if ShortCallsign then -callsign=callsignroot.." "..callnumbermajor -else -callsign=callsignroot.." "..callnumbermajor.." "..callnumberminor -end -end -return callsign -end -function GROUP:SetAsRecoveryTanker(CarrierGroup,Speed,ToKIAS,Altitude,Delay,LastWaypoint) -local speed=ToKIAS==true and UTILS.KnotsToAltKIAS(Speed,Altitude)or Speed -speed=UTILS.KnotsToMps(speed) -local alt=UTILS.FeetToMeters(Altitude) -local delay=Delay or 1 -local task=self:TaskRecoveryTanker(CarrierGroup,speed,alt,LastWaypoint) -self:SetTask(task,delay) -local tankertask=self:EnRouteTaskTanker() -self:PushTask(tankertask,delay+2) -return self -end -function GROUP:GetGroupSTN() -local tSTN={} -local units=self:GetUnits() -local gname=self:GetName() -gname=string.gsub(gname,"(#%d+)$","") -local report=REPORT:New() -report:Add("Link16 S/TN Report") -report:Add("Group: "..gname) -report:Add("==================") -for _,_unit in pairs(units)do -local unit=_unit -if unit and unit:IsAlive()then -local STN,VCL,VCN,Lead=unit:GetSTN() -local name=unit:GetName() -tSTN[name]={ -STN=STN, -VCL=VCL, -VCN=VCN, -Lead=Lead, -} -local lead=Lead==true and"(*)"or"" -report:Add(string.format("| %s%s %s %s",tostring(VCL),tostring(VCN),tostring(STN),lead)) -end -end -report:Add("==================") -local text=report:Text() -return tSTN,text -end -function GROUP:IsSAM() -local issam=false -local units=self:GetUnits() -for _,_unit in pairs(units or{})do -local unit=_unit -if unit:HasSEAD()and unit:IsGround()and(not unit:HasAttribute("Mobile AAA"))then -issam=true -break -end -end -return issam -end -function GROUP:IsAAA() -local issam=false -local units=self:GetUnits() -for _,_unit in pairs(units or{})do -local unit=_unit -local desc=unit:GetDesc()or{} -local attr=desc.attributes or{} -if unit:HasSEAD()then return false end -if attr["AAA"]or attr["SAM related"]then -issam=true -end -end -return issam -end -UNIT={ -ClassName="UNIT", -UnitName=nil, -GroupName=nil, -DCSUnit=nil, -} -function UNIT:Register(UnitName) -local self=BASE:Inherit(self,CONTROLLABLE:New(UnitName)) -self.UnitName=UnitName -local unit=Unit.getByName(self.UnitName) -if unit then -local group=unit:getGroup() -if group then -self.GroupName=group:getName() -end -self.DCSUnit=unit -end -self:SetEventPriority(3) -return self -end -function UNIT:Find(DCSUnit) -if DCSUnit then -local UnitName=DCSUnit:getName() -local UnitFound=_DATABASE:FindUnit(UnitName) -return UnitFound -end -return nil -end -function UNIT:FindByName(UnitName) -local UnitFound=_DATABASE:FindUnit(UnitName) -return UnitFound -end -function UNIT:FindByMatching(Pattern) -local GroupFound=nil -for name,group in pairs(_DATABASE.UNITS)do -if string.match(name,Pattern)then -GroupFound=group -break -end -end -return GroupFound -end -function UNIT:FindAllByMatching(Pattern) -local GroupsFound={} -for name,group in pairs(_DATABASE.UNITS)do -if string.match(name,Pattern)then -GroupsFound[#GroupsFound+1]=group -end -end -return GroupsFound -end -function UNIT:Name() -return self.UnitName -end -function UNIT:GetDCSObject() -local DCSUnit=Unit.getByName(self.UnitName) -if DCSUnit then -return DCSUnit -end -return nil -end -function UNIT:GetAltitude(FromGround) -local DCSUnit=Unit.getByName(self.UnitName) -if DCSUnit then -local altitude=0 -local point=DCSUnit:getPoint() -altitude=point.y -if FromGround then -local land=land.getHeight({x=point.x,y=point.z})or 0 -altitude=altitude-land -end -return altitude -end -return nil -end -function UNIT:ReSpawnAt(Coordinate,Heading) -self:T(self:Name()) -local SpawnGroupTemplate=UTILS.DeepCopy(_DATABASE:GetGroupTemplateFromUnitName(self:Name())) -self:T(SpawnGroupTemplate) -local SpawnGroup=self:GetGroup() -self:T({SpawnGroup=SpawnGroup}) -if SpawnGroup then -local Vec3=SpawnGroup:GetVec3() -SpawnGroupTemplate.x=Coordinate.x -SpawnGroupTemplate.y=Coordinate.z -self:F(#SpawnGroupTemplate.units) -for UnitID,UnitData in pairs(SpawnGroup:GetUnits()or{})do -local GroupUnit=UnitData -self:F(GroupUnit:GetName()) -if GroupUnit:IsAlive()then -local GroupUnitVec3=GroupUnit:GetVec3() -local GroupUnitHeading=GroupUnit:GetHeading() -SpawnGroupTemplate.units[UnitID].alt=GroupUnitVec3.y -SpawnGroupTemplate.units[UnitID].x=GroupUnitVec3.x -SpawnGroupTemplate.units[UnitID].y=GroupUnitVec3.z -SpawnGroupTemplate.units[UnitID].heading=GroupUnitHeading -self:F({UnitID,SpawnGroupTemplate.units[UnitID],SpawnGroupTemplate.units[UnitID]}) -end -end -end -for UnitTemplateID,UnitTemplateData in pairs(SpawnGroupTemplate.units)do -self:T({UnitTemplateData.name,self:Name()}) -SpawnGroupTemplate.units[UnitTemplateID].unitId=nil -if UnitTemplateData.name==self:Name()then -self:T("Adjusting") -SpawnGroupTemplate.units[UnitTemplateID].alt=Coordinate.y -SpawnGroupTemplate.units[UnitTemplateID].x=Coordinate.x -SpawnGroupTemplate.units[UnitTemplateID].y=Coordinate.z -SpawnGroupTemplate.units[UnitTemplateID].heading=Heading -self:F({UnitTemplateID,SpawnGroupTemplate.units[UnitTemplateID],SpawnGroupTemplate.units[UnitTemplateID]}) -else -self:F(SpawnGroupTemplate.units[UnitTemplateID].name) -local GroupUnit=UNIT:FindByName(SpawnGroupTemplate.units[UnitTemplateID].name) -if GroupUnit and GroupUnit:IsAlive()then -local GroupUnitVec3=GroupUnit:GetVec3() -local GroupUnitHeading=GroupUnit:GetHeading() -UnitTemplateData.alt=GroupUnitVec3.y -UnitTemplateData.x=GroupUnitVec3.x -UnitTemplateData.y=GroupUnitVec3.z -UnitTemplateData.heading=GroupUnitHeading -else -if SpawnGroupTemplate.units[UnitTemplateID].name~=self:Name()then -self:T("nilling") -SpawnGroupTemplate.units[UnitTemplateID].delete=true -end -end -end -end -local i=1 -while i<=#SpawnGroupTemplate.units do -local UnitTemplateData=SpawnGroupTemplate.units[i] -self:T(UnitTemplateData.name) -if UnitTemplateData.delete then -table.remove(SpawnGroupTemplate.units,i) -else -i=i+1 -end -end -SpawnGroupTemplate.groupId=nil -self:T(SpawnGroupTemplate) -_DATABASE:Spawn(SpawnGroupTemplate) -end -function UNIT:IsActive() -self:F2(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitIsActive=DCSUnit:isActive() -return UnitIsActive -end -return nil -end -function UNIT:IsExist() -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local exists=DCSUnit:isExist() -return exists -end -return nil -end -function UNIT:IsAlive() -self:F3(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit and DCSUnit:isExist()then -local UnitIsAlive=DCSUnit:isActive() -return UnitIsAlive -end -return nil -end -function UNIT:IsDead() -return not self:IsAlive() -end -function UNIT:GetCallsign() -self:F2(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitCallSign=DCSUnit:getCallsign() -if UnitCallSign==""then -UnitCallSign=DCSUnit:getName() -end -return UnitCallSign -end -self:F(self.ClassName.." "..self.UnitName.." not found!") -return nil -end -function UNIT:IsPlayer() -local group=self:GetGroup() -if not group then return false end -local units=group:GetTemplate().units -for _,unit in pairs(units)do -if unit.name==self:GetName()and(unit.skill=="Client"or unit.skill=="Player")then -return true -end -end -return false -end -function UNIT:GetPlayerName() -self:F(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local PlayerName=DCSUnit:getPlayerName() -return PlayerName -end -return nil -end -function UNIT:IsClient() -if _DATABASE.CLIENTS[self.UnitName]then -return true -end -return false -end -function UNIT:GetClient() -local client=_DATABASE.CLIENTS[self.UnitName] -if client then -return client -end -return nil -end -function UNIT:GetNatoReportingName() -local typename=self:GetTypeName() -return UTILS.GetReportingName(typename) -end -function UNIT:GetNumber() -self:F2(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitNumber=DCSUnit:getNumber() -return UnitNumber -end -return nil -end -function UNIT:GetSpeedMax() -self:F2(self.UnitName) -local Desc=self:GetDesc() -if Desc then -local SpeedMax=Desc.speedMax -return SpeedMax*3.6 -end -return 0 -end -function UNIT:GetRange() -self:F2(self.UnitName) -local Desc=self:GetDesc() -if Desc then -local Range=Desc.range -if Range then -Range=Range*1000 -else -Range=10000000 -end -return Range -end -return nil -end -function UNIT:IsRefuelable() -self:F2(self.UnitName) -local refuelable=self:HasAttribute("Refuelable") -local system=nil -local Desc=self:GetDesc() -if Desc and Desc.tankerType then -system=Desc.tankerType -end -return refuelable,system -end -function UNIT:IsTanker() -self:F2(self.UnitName) -local tanker=self:HasAttribute("Tankers") -local system=nil -if tanker then -local Desc=self:GetDesc() -if Desc and Desc.tankerType then -system=Desc.tankerType -end -local typename=self:GetTypeName() -if typename=="IL-78M"then -system=1 -elseif typename=="KC130"or typename=="KC130J"then -system=1 -elseif typename=="KC135BDA"then -system=1 -elseif typename=="KC135MPRS"then -system=1 -elseif typename=="S-3B Tanker"then -system=1 -elseif typename=="KC_10_Extender"then -system=1 -elseif typename=="KC_10_Extender_D"then -system=0 -end -end -return tanker,system -end -function UNIT:IsAmmoSupply() -local typename=self:GetTypeName() -if typename=="M 818"then -return true -elseif typename=="Ural-375"then -return true -elseif typename=="ZIL-135"then -return true -end -return false -end -function UNIT:IsFuelSupply() -local typename=self:GetTypeName() -if typename=="M978 HEMTT Tanker"then -return true -elseif typename=="ATMZ-5"then -return true -elseif typename=="ATMZ-10"then -return true -elseif typename=="ATZ-5"then -return true -end -return false -end -function UNIT:GetGroup() -self:F2(self.UnitName) -local UnitGroup=GROUP:FindByName(self.GroupName) -if UnitGroup then -return UnitGroup -else -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local grp=DCSUnit:getGroup() -if grp then -local UnitGroup=GROUP:FindByName(grp:getName()) -return UnitGroup -end -end -end -return nil -end -function UNIT:GetPrefix() -self:F2(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitPrefix=string.match(self.UnitName,".*#"):sub(1,-2) -self:T3(UnitPrefix) -return UnitPrefix -end -return nil -end -function UNIT:GetAmmo() -self:F2(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitAmmo=DCSUnit:getAmmo() -return UnitAmmo -end -return nil -end -function UNIT:SetUnitInternalCargo(mass) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -trigger.action.setUnitInternalCargo(DCSUnit:getName(),mass) -end -return self -end -function UNIT:GetAmmunition() -local nammo=0 -local nshells=0 -local nrockets=0 -local nmissiles=0 -local nbombs=0 -local narti=0 -local unit=self -local ammotable=unit:GetAmmo() -if ammotable then -local weapons=#ammotable -for w=1,weapons do -local Nammo=ammotable[w]["count"] -local Tammo=ammotable[w]["desc"]["typeName"] -local Category=ammotable[w].desc.category -local MissileCategory=nil -if Category==Weapon.Category.MISSILE then -MissileCategory=ammotable[w].desc.missileCategory -end -if Category==Weapon.Category.SHELL then -nshells=nshells+Nammo -if ammotable[w].desc.warhead and ammotable[w].desc.warhead.explosiveMass and ammotable[w].desc.warhead.explosiveMass>0 then -narti=narti+Nammo -end -elseif Category==Weapon.Category.ROCKET then -nrockets=nrockets+Nammo -elseif Category==Weapon.Category.BOMB then -nbombs=nbombs+Nammo -elseif Category==Weapon.Category.MISSILE then -if MissileCategory==Weapon.MissileCategory.AAM then -nmissiles=nmissiles+Nammo -elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then -nmissiles=nmissiles+Nammo -elseif MissileCategory==Weapon.MissileCategory.BM then -nmissiles=nmissiles+Nammo -elseif MissileCategory==Weapon.MissileCategory.OTHER then -nmissiles=nmissiles+Nammo -elseif MissileCategory==Weapon.MissileCategory.SAM then -nmissiles=nmissiles+Nammo -elseif MissileCategory==Weapon.MissileCategory.CRUISE then -nmissiles=nmissiles+Nammo -end -end -end -end -nammo=nshells+nrockets+nmissiles+nbombs -return nammo,nshells,nrockets,nbombs,nmissiles,narti -end -function UNIT:GetSensors() -self:F2(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitSensors=DCSUnit:getSensors() -return UnitSensors -end -return nil -end -function UNIT:HasSensors(...) -self:F2(arg) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local HasSensors=DCSUnit:hasSensors(unpack(arg)) -return HasSensors -end -return nil -end -function UNIT:HasSEAD() -self:F2() -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitSEADAttributes=DCSUnit:getDesc().attributes -local HasSEAD=false -if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"]and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"]==true or -UnitSEADAttributes["RADAR_BAND2_FOR_ARM"]and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"]==true or -UnitSEADAttributes["Optical Tracker"]and UnitSEADAttributes["Optical Tracker"]==true -then -HasSEAD=true -end -return HasSEAD -end -return nil -end -function UNIT:GetRadar() -self:F2(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitRadarOn,UnitRadarObject=DCSUnit:getRadar() -return UnitRadarOn,UnitRadarObject -end -return nil,nil -end -function UNIT:GetFuel() -self:F3(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitFuel=DCSUnit:getFuel() -return UnitFuel -end -return nil -end -function UNIT:GetUnits() -self:F3({self.UnitName}) -local DCSUnit=self:GetDCSObject() -local Units={} -if DCSUnit then -Units[1]=UNIT:Find(DCSUnit) -self:T3(Units) -return Units -end -return nil -end -function UNIT:GetLife() -self:F2(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit and DCSUnit:isExist()then -local UnitLife=DCSUnit:getLife() -return UnitLife -end -return-1 -end -function UNIT:GetLife0() -self:F2(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitLife0=DCSUnit:getLife0() -return UnitLife0 -end -return 0 -end -function UNIT:GetLifeRelative() -self:F2(self.UnitName) -if self and self:IsAlive()then -local life0=self:GetLife0() -local lifeN=self:GetLife() -return lifeN/life0 -end -return-1 -end -function UNIT:GetDamageRelative() -self:F2(self.UnitName) -if self and self:IsAlive()then -return 1-self:GetLifeRelative() -end -return 1 -end -function UNIT:GetDrawArgumentValue(AnimationArgument) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local value=DCSUnit:getDrawArgumentValue(AnimationArgument or 0) -return value -end -return 0 -end -function UNIT:GetUnitCategory() -self:F3(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -return DCSUnit:getDesc().category -end -return nil -end -function UNIT:GetCategoryName() -self:F3(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local CategoryNames={ -[Unit.Category.AIRPLANE]="Airplane", -[Unit.Category.HELICOPTER]="Helicopter", -[Unit.Category.GROUND_UNIT]="Ground Unit", -[Unit.Category.SHIP]="Ship", -[Unit.Category.STRUCTURE]="Structure", -} -local UnitCategory=DCSUnit:getDesc().category -self:T3(UnitCategory) -return CategoryNames[UnitCategory] -end -return nil -end -function UNIT:GetThreatLevel() -local ThreatLevel=0 -local ThreatText="" -local Descriptor=self:GetDesc() -if Descriptor then -local Attributes=Descriptor.attributes -if self:IsGround()then -local ThreatLevels={ -"Unarmed", -"Infantry", -"Old Tanks & APCs", -"Tanks & IFVs without ATGM", -"Tanks & IFV with ATGM", -"Modern Tanks", -"AAA", -"IR Guided SAMs", -"SR SAMs", -"MR SAMs", -"LR SAMs" -} -if Attributes["LR SAM"]then ThreatLevel=10 -elseif Attributes["MR SAM"]then ThreatLevel=9 -elseif Attributes["SR SAM"]and -not Attributes["IR Guided SAM"]then ThreatLevel=8 -elseif(Attributes["SR SAM"]or Attributes["MANPADS"])and -Attributes["IR Guided SAM"]then ThreatLevel=7 -elseif Attributes["AAA"]then ThreatLevel=6 -elseif Attributes["Modern Tanks"]then ThreatLevel=5 -elseif(Attributes["Tanks"]or Attributes["IFV"])and -Attributes["ATGM"]then ThreatLevel=4 -elseif(Attributes["Tanks"]or Attributes["IFV"])and -not Attributes["ATGM"]then ThreatLevel=3 -elseif Attributes["Old Tanks"]or Attributes["APC"]or Attributes["Artillery"]then ThreatLevel=2 -elseif Attributes["Infantry"]or Attributes["EWR"]then ThreatLevel=1 -end -ThreatText=ThreatLevels[ThreatLevel+1] -end -if self:IsAir()then -local ThreatLevels={ -"Unarmed", -"Tanker", -"AWACS", -"Transport Helicopter", -"UAV", -"Bomber", -"Strategic Bomber", -"Attack Helicopter", -"Battleplane", -"Multirole Fighter", -"Fighter" -} -if Attributes["Fighters"]then ThreatLevel=10 -elseif Attributes["Multirole fighters"]then ThreatLevel=9 -elseif Attributes["Battleplanes"]then ThreatLevel=8 -elseif Attributes["Attack helicopters"]then ThreatLevel=7 -elseif Attributes["Strategic bombers"]then ThreatLevel=6 -elseif Attributes["Bombers"]then ThreatLevel=5 -elseif Attributes["UAVs"]then ThreatLevel=4 -elseif Attributes["Transport helicopters"]then ThreatLevel=3 -elseif Attributes["AWACS"]then ThreatLevel=2 -elseif Attributes["Tankers"]then ThreatLevel=1 -end -ThreatText=ThreatLevels[ThreatLevel+1] -end -if self:IsShip()then -local ThreatLevels={ -"Unarmed ship", -"Light armed ships", -"Corvettes", -"", -"Frigates", -"", -"Cruiser", -"", -"Destroyer", -"", -"Aircraft Carrier" -} -if Attributes["Aircraft Carriers"]then ThreatLevel=10 -elseif Attributes["Destroyers"]then ThreatLevel=8 -elseif Attributes["Cruisers"]then ThreatLevel=6 -elseif Attributes["Frigates"]then ThreatLevel=4 -elseif Attributes["Corvettes"]then ThreatLevel=2 -elseif Attributes["Light armed ships"]then ThreatLevel=1 -end -ThreatText=ThreatLevels[ThreatLevel+1] -end -end -return ThreatLevel,ThreatText -end -function UNIT:Explode(power,delay) -power=power or 100 -local DCSUnit=self:GetDCSObject() -if DCSUnit then -if delay and delay>0 then -SCHEDULER:New(nil,self.Explode,{self,power},delay) -else -self:GetCoordinate():Explosion(power) -end -return self -end -return nil -end -function UNIT:OtherUnitInRadius(AwaitUnit,Radius) -self:F2({self.UnitName,AwaitUnit.UnitName,Radius}) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitVec3=self:GetVec3() -local AwaitUnitVec3=AwaitUnit:GetVec3() -if(((UnitVec3.x-AwaitUnitVec3.x)^2+(UnitVec3.z-AwaitUnitVec3.z)^2)^0.5<=Radius)then -self:T3("true") -return true -else -self:T3("false") -return false -end -end -return nil -end -function UNIT:IsFriendly(FriendlyCoalition) -self:F2() -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitCoalition=DCSUnit:getCoalition() -self:T3({UnitCoalition,FriendlyCoalition}) -local IsFriendlyResult=(UnitCoalition==FriendlyCoalition) -self:F(IsFriendlyResult) -return IsFriendlyResult -end -return nil -end -function UNIT:IsShip() -self:F2() -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitDescriptor=DCSUnit:getDesc() -self:T3({UnitDescriptor.category,Unit.Category.SHIP}) -local IsShipResult=(UnitDescriptor.category==Unit.Category.SHIP) -self:T3(IsShipResult) -return IsShipResult -end -return nil -end -function UNIT:InAir(NoHeloCheck) -self:F2(self.UnitName) -local DCSUnit=self:GetDCSObject() -if DCSUnit then -local UnitInAir=DCSUnit:inAir() -local UnitCategory=DCSUnit:getDesc().category -if UnitInAir==true and UnitCategory==Unit.Category.HELICOPTER and(not NoHeloCheck)then -local VelocityVec3=DCSUnit:getVelocity() -local Velocity=UTILS.VecNorm(VelocityVec3) -local Coordinate=DCSUnit:getPoint() -local LandHeight=land.getHeight({x=Coordinate.x,y=Coordinate.z}) -local Height=Coordinate.y-LandHeight -if Velocity<1 and Height<=60 then -UnitInAir=false -end -end -self:T3(UnitInAir) -return UnitInAir -end -return nil -end -do -function UNIT:HandleEvent(EventID,EventFunction) -self:EventDispatcher():OnEventForUnit(self:GetName(),EventFunction,self,EventID) -return self -end -function UNIT:UnHandleEvent(EventID) -self:EventDispatcher():RemoveEvent(self,EventID) -return self -end -function UNIT:ResetEvents() -self:EventDispatcher():Reset(self) -return self -end -end -do -function UNIT:IsDetected(TargetUnit) -local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity=self:IsTargetDetected(TargetUnit:GetDCSObject()) -return TargetIsDetected -end -function UNIT:IsLOS(TargetUnit) -local IsLOS=self:GetPointVec3():IsLOS(TargetUnit:GetPointVec3()) -return IsLOS -end -function UNIT:KnowUnit(TargetUnit,TypeKnown,DistanceKnown) -if TypeKnown~=false then -TypeKnown=true -end -if DistanceKnown~=false then -DistanceKnown=true -end -local DCSControllable=self:GetDCSObject() -if DCSControllable then -local Controller=DCSControllable:getController() -if Controller then -local object=TargetUnit:GetDCSObject() -if object then -self:I(string.format("Unit %s now knows target unit %s. Type known=%s, distance known=%s",self:GetName(),TargetUnit:GetName(),tostring(TypeKnown),tostring(DistanceKnown))) -Controller:knowTarget(object,TypeKnown,DistanceKnown) -end -end -end -end -end -function UNIT:GetTemplate() -local group=self:GetGroup() -local name=self:GetName() -if group then -local template=group:GetTemplate() -if template then -for _,unit in pairs(template.units)do -if unit.name==name then -return UTILS.DeepCopy(unit) -end -end -end -end -return nil -end -function UNIT:GetTemplatePayload() -local unit=self:GetTemplate() -if unit then -return unit.payload -end -return nil -end -function UNIT:GetTemplatePylons() -local payload=self:GetTemplatePayload() -if payload then -return payload.pylons -end -return nil -end -function UNIT:GetTemplateFuel() -local payload=self:GetTemplatePayload() -if payload then -return payload.fuel -end -return nil -end -function UNIT:EnableEmission(switch) -self:F2(self.UnitName) -local switch=switch or false -local DCSUnit=self:GetDCSObject() -if DCSUnit then -DCSUnit:enableEmission(switch) -end -return self -end -function UNIT:GetSkill() -self:F2(self.UnitName) -local name=self.UnitName -local skill=_DATABASE.Templates.Units[name].Template.skill or"Random" -return skill -end -function UNIT:GetSTN() -self:F2(self.UnitName) -local STN=nil -local VCL=nil -local VCN=nil -local FGL=false -local template=self:GetTemplate() -if template.AddPropAircraft then -if template.AddPropAircraft.STN_L16 then -STN=template.AddPropAircraft.STN_L16 -elseif template.AddPropAircraft.SADL_TN then -STN=template.AddPropAircraft.SADL_TN -end -VCN=template.AddPropAircraft.VoiceCallsignNumber -VCL=template.AddPropAircraft.VoiceCallsignLabel -end -if template.datalinks and template.datalinks.Link16 and template.datalinks.Link16.settings then -FGL=template.datalinks.Link16.settings.flightLead -end -if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then -FGL=template.datalinks.SADL.settings.flightLead -end -return STN,VCL,VCN,FGL -end -CLIENT={ -ClassName="CLIENT", -ClientName=nil, -ClientAlive=false, -ClientTransport=false, -ClientBriefingShown=false, -_Menus={}, -_Tasks={}, -Messages={}, -Players={}, -} -function CLIENT:Find(DCSUnit,Error) -local ClientName=DCSUnit:getName() -local ClientFound=_DATABASE:FindClient(ClientName) -if ClientFound then -ClientFound:F(ClientName) -return ClientFound -end -if not Error then -error("CLIENT not found for: "..ClientName) -end -end -function CLIENT:FindByPlayerName(Name) -local foundclient=nil -_DATABASE:ForEachClient( -function(client) -if client:GetPlayerName()==Name then -foundclient=client -end -end -) -return foundclient -end -function CLIENT:FindByName(ClientName,ClientBriefing,Error) -local ClientFound=_DATABASE:FindClient(ClientName) -if ClientFound then -ClientFound:F({ClientName,ClientBriefing}) -ClientFound:AddBriefing(ClientBriefing) -ClientFound.MessageSwitch=true -return ClientFound -end -if not Error then -error("CLIENT not found for: "..ClientName) -end -end -function CLIENT:Register(ClientName) -local self=BASE:Inherit(self,UNIT:Register(ClientName)) -self.ClientName=ClientName -self.MessageSwitch=true -self.ClientAlive2=false -return self -end -function CLIENT:Transport() -self.ClientTransport=true -return self -end -function CLIENT:AddBriefing(ClientBriefing) -self.ClientBriefing=ClientBriefing -self.ClientBriefingShown=false -return self -end -function CLIENT:AddPlayer(PlayerName) -table.insert(self.Players,PlayerName) -return self -end -function CLIENT:GetPlayers() -return self.Players -end -function CLIENT:GetPlayer() -if#self.Players>0 then -return self.Players[1] -end -return nil -end -function CLIENT:RemovePlayer(PlayerName) -for i,playername in pairs(self.Players)do -if PlayerName==playername then -table.remove(self.Players,i) -break -end -end -return self -end -function CLIENT:RemovePlayers() -self.Players={} -return self -end -function CLIENT:ShowBriefing() -if not self.ClientBriefingShown then -self.ClientBriefingShown=true -local Briefing="" -if self.ClientBriefing and self.ClientBriefing~=""then -Briefing=Briefing..self.ClientBriefing -self:Message(Briefing,60,"Briefing") -end -end -return self -end -function CLIENT:ShowMissionBriefing(MissionBriefing) -self:F({self.ClientName}) -if MissionBriefing then -self:Message(MissionBriefing,60,"Mission Briefing") -end -return self -end -function CLIENT:Reset(ClientName) -self:F() -self._Menus={} -end -function CLIENT:IsMultiSeated() -self:F(self.ClientName) -local ClientMultiSeatedTypes={ -["Mi-8MT"]="Mi-8MT", -["UH-1H"]="UH-1H", -["P-51B"]="P-51B" -} -if self:IsAlive()then -local ClientTypeName=self:GetClientGroupUnit():GetTypeName() -if ClientMultiSeatedTypes[ClientTypeName]then -return true -end -end -return false -end -function CLIENT:Alive(CallBackFunction,...) -self:F() -self.ClientCallBack=CallBackFunction -self.ClientParameters=arg -self.AliveCheckScheduler=SCHEDULER:New(self,self._AliveCheckScheduler,{"Client Alive "..self.ClientName},0.1,5,0.5) -self.AliveCheckScheduler:NoTrace() -return self -end -function CLIENT:_AliveCheckScheduler(SchedulerName) -self:F3({SchedulerName,self.ClientName,self.ClientAlive2,self.ClientBriefingShown,self.ClientCallBack}) -if self:IsAlive()then -if self.ClientAlive2==false then -self:ShowBriefing() -if self.ClientCallBack then -self:T("Calling Callback function") -self.ClientCallBack(self,unpack(self.ClientParameters)) -end -self.ClientAlive2=true -end -else -if self.ClientAlive2==true then -self.ClientAlive2=false -end -end -return true -end -function CLIENT:GetDCSGroup() -self:F3() -local ClientUnit=Unit.getByName(self.ClientName) -local CoalitionsData={AlivePlayersRed=coalition.getPlayers(coalition.side.RED),AlivePlayersBlue=coalition.getPlayers(coalition.side.BLUE)} -for CoalitionId,CoalitionData in pairs(CoalitionsData)do -self:T3({"CoalitionData:",CoalitionData}) -for UnitId,UnitData in pairs(CoalitionData)do -self:T3({"UnitData:",UnitData}) -if UnitData and UnitData:isExist()then -if ClientUnit then -local ClientGroup=ClientUnit:getGroup() -if ClientGroup then -self:T3("ClientGroup = "..self.ClientName) -if ClientGroup:isExist()and UnitData:getGroup():isExist()then -if ClientGroup:getID()==UnitData:getGroup():getID()then -self:T3("Normal logic") -self:T3(self.ClientName.." : group found!") -self.ClientGroupID=ClientGroup:getID() -self.ClientGroupName=ClientGroup:getName() -return ClientGroup -end -else -self:T3("Bug 1.5 logic") -local ClientGroupTemplate=_DATABASE.Templates.Units[self.ClientName].GroupTemplate -self.ClientGroupID=ClientGroupTemplate.groupId -self.ClientGroupName=_DATABASE.Templates.Units[self.ClientName].GroupName -self:T3(self.ClientName.." : group found in bug 1.5 resolvement logic!") -return ClientGroup -end -end -else -end -end -end -end -if ClientUnit then -local ClientGroup=ClientUnit:getGroup() -if ClientGroup then -self:T3("ClientGroup = "..self.ClientName) -if ClientGroup:isExist()then -self:T3("Normal logic") -self:T3(self.ClientName.." : group found!") -return ClientGroup -end -end -end -self.ClientGroupID=nil -self.ClientGroupName=nil -return nil -end -function CLIENT:GetClientGroupID() -self:GetDCSGroup() -return self.ClientGroupID -end -function CLIENT:GetClientGroupName() -self:GetDCSGroup() -return self.ClientGroupName -end -function CLIENT:GetClientGroupUnit() -self:F2() -local ClientDCSUnit=Unit.getByName(self.ClientName) -self:T(self.ClientDCSUnit) -if ClientDCSUnit and ClientDCSUnit:isExist()then -local ClientUnit=_DATABASE:FindUnit(self.ClientName) -return ClientUnit -end -return nil -end -function CLIENT:GetClientGroupDCSUnit() -self:F2() -local ClientDCSUnit=Unit.getByName(self.ClientName) -if ClientDCSUnit and ClientDCSUnit:isExist()then -self:T2(ClientDCSUnit) -return ClientDCSUnit -end -end -function CLIENT:IsTransport() -self:F() -return self.ClientTransport -end -function CLIENT:ShowCargo() -self:F() -local CargoMsg="" -for CargoName,Cargo in pairs(CARGOS)do -if self==Cargo:IsLoadedInClient()then -CargoMsg=CargoMsg..Cargo.CargoName.." Type:"..Cargo.CargoType.." Weight: "..Cargo.CargoWeight.."\n" -end -end -if CargoMsg==""then -CargoMsg="empty" -end -self:Message(CargoMsg,15,"Co-Pilot: Cargo Status",30) -end -function CLIENT:Message(Message,MessageDuration,MessageCategory,MessageInterval,MessageID) -self:F({Message,MessageDuration,MessageCategory,MessageInterval}) -if self.MessageSwitch==true then -if MessageCategory==nil then -MessageCategory="Messages" -end -if MessageID~=nil then -if self.Messages[MessageID]==nil then -self.Messages[MessageID]={} -self.Messages[MessageID].MessageId=MessageID -self.Messages[MessageID].MessageTime=timer.getTime() -self.Messages[MessageID].MessageDuration=MessageDuration -if MessageInterval==nil then -self.Messages[MessageID].MessageInterval=600 -else -self.Messages[MessageID].MessageInterval=MessageInterval -end -MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self) -else -if self:GetClientGroupDCSUnit()and not self:GetClientGroupDCSUnit():inAir()then -if timer.getTime()-self.Messages[MessageID].MessageTime>=self.Messages[MessageID].MessageDuration+10 then -MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self) -self.Messages[MessageID].MessageTime=timer.getTime() -end -else -if timer.getTime()-self.Messages[MessageID].MessageTime>=self.Messages[MessageID].MessageDuration+self.Messages[MessageID].MessageInterval then -MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self) -self.Messages[MessageID].MessageTime=timer.getTime() -end -end -end -else -MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self) -end -end -end -function CLIENT:GetUCID() -local PID=NET.GetPlayerIDByName(nil,self:GetPlayerName()) -return net.get_player_info(tonumber(PID),'ucid') -end -function CLIENT:GetPlayerInfo(Attribute) -local PID=NET.GetPlayerIDByName(nil,self:GetPlayerName()) -if PID then -return net.get_player_info(tonumber(PID),Attribute) -else -return nil -end -end -STATIC={ -ClassName="STATIC", -} -function STATIC:Register(StaticName) -local self=BASE:Inherit(self,POSITIONABLE:New(StaticName)) -self.StaticName=StaticName -local DCSStatic=StaticObject.getByName(self.StaticName) -if DCSStatic then -local Life0=DCSStatic:getLife()or 1 -self.Life0=Life0 -end -return self -end -function STATIC:GetLife0() -return self.Life0 or 1 -end -function STATIC:GetLife() -local DCSStatic=StaticObject.getByName(self.StaticName) -if DCSStatic then -return DCSStatic:getLife()or 1 -end -return nil -end -function STATIC:Find(DCSStatic) -local StaticName=DCSStatic:getName() -local StaticFound=_DATABASE:FindStatic(StaticName) -return StaticFound -end -function STATIC:FindByName(StaticName,RaiseError) -local StaticFound=_DATABASE:FindStatic(StaticName) -self.StaticName=StaticName -if StaticFound then -return StaticFound -end -if RaiseError==nil or RaiseError==true then -error("STATIC not found for: "..StaticName) -end -return nil -end -function STATIC:Destroy(GenerateEvent) -self:F2(self.ObjectName) -local DCSObject=self:GetDCSObject() -if DCSObject then -local StaticName=DCSObject:getName() -self:F({StaticName=StaticName}) -if GenerateEvent and GenerateEvent==true then -if self:IsAir()then -self:CreateEventCrash(timer.getTime(),DCSObject) -else -self:CreateEventDead(timer.getTime(),DCSObject) -end -elseif GenerateEvent==false then -else -self:CreateEventRemoveUnit(timer.getTime(),DCSObject) -end -DCSObject:destroy() -return true -end -return nil -end -function STATIC:GetDCSObject() -local DCSStatic=StaticObject.getByName(self.StaticName) -if DCSStatic then -return DCSStatic -end -return nil -end -function STATIC:GetUnits() -self:F2({self.StaticName}) -local DCSStatic=self:GetDCSObject() -local Statics={} -if DCSStatic then -Statics[1]=STATIC:Find(DCSStatic) -self:T3(Statics) -return Statics -end -return nil -end -function STATIC:GetThreatLevel() -return 1,"Static" -end -function STATIC:SpawnAt(Coordinate,Heading,Delay) -Heading=Heading or 0 -if Delay and Delay>0 then -SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay) -else -local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) -SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName) -end -return self -end -function STATIC:ReSpawn(CountryID,Delay) -if Delay and Delay>0 then -SCHEDULER:New(nil,self.ReSpawn,{self,CountryID},Delay) -else -CountryID=CountryID or self:GetCountry() -local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID) -SpawnStatic:Spawn(nil,self.StaticName) -end -return self -end -function STATIC:ReSpawnAt(Coordinate,Heading,Delay) -if Delay and Delay>0 then -SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay) -else -local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry()) -SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName) -end -return self -end -AIRBASE={ -ClassName="AIRBASE", -CategoryName={ -[Airbase.Category.AIRDROME]="Airdrome", -[Airbase.Category.HELIPAD]="Helipad", -[Airbase.Category.SHIP]="Ship", -}, -activerwyno=nil, -} -AIRBASE.Caucasus={ -["Gelendzhik"]="Gelendzhik", -["Krasnodar_Pashkovsky"]="Krasnodar-Pashkovsky", -["Sukhumi_Babushara"]="Sukhumi-Babushara", -["Gudauta"]="Gudauta", -["Batumi"]="Batumi", -["Senaki_Kolkhi"]="Senaki-Kolkhi", -["Kobuleti"]="Kobuleti", -["Kutaisi"]="Kutaisi", -["Tbilisi_Lochini"]="Tbilisi-Lochini", -["Soganlug"]="Soganlug", -["Vaziani"]="Vaziani", -["Anapa_Vityazevo"]="Anapa-Vityazevo", -["Krasnodar_Center"]="Krasnodar-Center", -["Novorossiysk"]="Novorossiysk", -["Krymsk"]="Krymsk", -["Maykop_Khanskaya"]="Maykop-Khanskaya", -["Sochi_Adler"]="Sochi-Adler", -["Mineralnye_Vody"]="Mineralnye Vody", -["Nalchik"]="Nalchik", -["Mozdok"]="Mozdok", -["Beslan"]="Beslan", -} -AIRBASE.Nevada={ -["Creech_AFB"]="Creech", -["Groom_Lake_AFB"]="Groom Lake", -["McCarran_International_Airport"]="McCarran International", -["Nellis_AFB"]="Nellis", -["Beatty_Airport"]="Beatty", -["Boulder_City_Airport"]="Boulder City", -["Echo_Bay"]="Echo Bay", -["Henderson_Executive_Airport"]="Henderson Executive", -["Jean_Airport"]="Jean", -["Laughlin_Airport"]="Laughlin", -["Lincoln_County"]="Lincoln County", -["Mesquite"]="Mesquite", -["Mina_Airport"]="Mina", -["North_Las_Vegas"]="North Las Vegas", -["Pahute_Mesa_Airstrip"]="Pahute Mesa", -["Tonopah_Airport"]="Tonopah", -["Tonopah_Test_Range_Airfield"]="Tonopah Test Range", -} -AIRBASE.Normandy={ -["Saint_Pierre_du_Mont"]="Saint Pierre du Mont", -["Lignerolles"]="Lignerolles", -["Cretteville"]="Cretteville", -["Maupertus"]="Maupertus", -["Brucheville"]="Brucheville", -["Meautis"]="Meautis", -["Cricqueville_en_Bessin"]="Cricqueville-en-Bessin", -["Lessay"]="Lessay", -["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer", -["Biniville"]="Biniville", -["Cardonville"]="Cardonville", -["Deux_Jumeaux"]="Deux Jumeaux", -["Chippelle"]="Chippelle", -["Beuzeville"]="Beuzeville", -["Azeville"]="Azeville", -["Picauville"]="Picauville", -["Le_Molay"]="Le Molay", -["Longues_sur_Mer"]="Longues-sur-Mer", -["Carpiquet"]="Carpiquet", -["Bazenville"]="Bazenville", -["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer", -["Beny_sur_Mer"]="Beny-sur-Mer", -["Rucqueville"]="Rucqueville", -["Sommervieu"]="Sommervieu", -["Lantheuil"]="Lantheuil", -["Evreux"]="Evreux", -["Chailey"]="Chailey", -["Needs_Oar_Point"]="Needs Oar Point", -["Funtington"]="Funtington", -["Tangmere"]="Tangmere", -["Ford"]="Ford", -["Argentan"]="Argentan", -["Goulet"]="Goulet", -["Barville"]="Barville", -["Essay"]="Essay", -["Hauterive"]="Hauterive", -["Lymington"]="Lymington", -["Vrigny"]="Vrigny", -["Odiham"]="Odiham", -["Conches"]="Conches", -["West_Malling"]="West Malling", -["Villacoublay"]="Villacoublay", -["Kenley"]="Kenley", -["Beauvais_Tille"]="Beauvais-Tille", -["Cormeilles_en_Vexin"]="Cormeilles-en-Vexin", -["Creil"]="Creil", -["Guyancourt"]="Guyancourt", -["Lonrai"]="Lonrai", -["Dinan_Trelivan"]="Dinan-Trelivan", -["Heathrow"]="Heathrow", -["Fecamp_Benouville"]="Fecamp-Benouville", -["Farnborough"]="Farnborough", -["Friston"]="Friston", -["Deanland "]="Deanland ", -["Triqueville"]="Triqueville", -["Poix"]="Poix", -["Orly"]="Orly", -["Stoney_Cross"]="Stoney Cross", -["Amiens_Glisy"]="Amiens-Glisy", -["Ronai"]="Ronai", -["Rouen_Boos"]="Rouen-Boos", -["Deauville"]="Deauville", -["Saint_Aubin"]="Saint-Aubin", -["Flers"]="Flers", -["Avranches_Le_Val_Saint_Pere"]="Avranches Le Val-Saint-Pere", -["Gravesend"]="Gravesend", -["Beaumont_le_Roger"]="Beaumont-le-Roger", -["Broglie"]="Broglie", -["Bernay_Saint_Martin"]="Bernay Saint Martin", -["Saint_Andre_de_lEure"]="Saint-Andre-de-lEure", -["Biggin_Hill"]="Biggin Hill", -["Manston"]="Manston", -["Detling"]="Detling", -["Lympne"]="Lympne", -["Abbeville_Drucat"]="Abbeville Drucat", -["Merville_Calonne"]="Merville Calonne", -["Saint_Omer_Wizernes"]="Saint-Omer Wizernes", -} -AIRBASE.PersianGulf={ -["Abu_Dhabi_International_Airport"]="Abu Dhabi Intl", -["Abu_Musa_Island_Airport"]="Abu Musa Island", -["Al_Ain_International_Airport"]="Al Ain Intl", -["Al_Bateen_Airport"]="Al-Bateen", -["Al_Dhafra_AB"]="Al Dhafra AFB", -["Al_Maktoum_Intl"]="Al Maktoum Intl", -["Al_Minhad_AB"]="Al Minhad AFB", -["Bandar_Abbas_Intl"]="Bandar Abbas Intl", -["Bandar_Lengeh"]="Bandar Lengeh", -["Bandar_e_Jask_airfield"]="Bandar-e-Jask", -["Dubai_Intl"]="Dubai Intl", -["Fujairah_Intl"]="Fujairah Intl", -["Havadarya"]="Havadarya", -["Jiroft_Airport"]="Jiroft", -["Kerman_Airport"]="Kerman", -["Khasab"]="Khasab", -["Kish_International_Airport"]="Kish Intl", -["Lar_Airbase"]="Lar", -["Lavan_Island_Airport"]="Lavan Island", -["Liwa_Airbase"]="Liwa AFB", -["Qeshm_Island"]="Qeshm Island", -["Ras_Al_Khaimah"]="Ras Al Khaimah Intl", -["Sas_Al_Nakheel_Airport"]="Sas Al Nakheel", -["Sharjah_Intl"]="Sharjah Intl", -["Shiraz_International_Airport"]="Shiraz Intl", -["Sir_Abu_Nuayr"]="Sir Abu Nuayr", -["Sirri_Island"]="Sirri Island", -["Tunb_Island_AFB"]="Tunb Island AFB", -["Tunb_Kochak"]="Tunb Kochak", -} -AIRBASE.TheChannel={ -["Abbeville_Drucat"]="Abbeville Drucat", -["Merville_Calonne"]="Merville Calonne", -["Saint_Omer_Longuenesse"]="Saint Omer Longuenesse", -["Dunkirk_Mardyck"]="Dunkirk Mardyck", -["Manston"]="Manston", -["Hawkinge"]="Hawkinge", -["Lympne"]="Lympne", -["Detling"]="Detling", -["High_Halden"]="High Halden", -["Biggin_Hill"]="Biggin Hill", -["Eastchurch"]="Eastchurch", -["Headcorn"]="Headcorn", -} -AIRBASE.Syria={ -["Kuweires"]="Kuweires", -["Marj_Ruhayyil"]="Marj Ruhayyil", -["Kiryat_Shmona"]="Kiryat Shmona", -["Marj_as_Sultan_North"]="Marj as Sultan North", -["Eyn_Shemer"]="Eyn Shemer", -["Incirlik"]="Incirlik", -["Damascus"]="Damascus", -["Bassel_Al_Assad"]="Bassel Al-Assad", -["Rosh_Pina"]="Rosh Pina", -["Aleppo"]="Aleppo", -["Al_Qusayr"]="Al Qusayr", -["Wujah_Al_Hajar"]="Wujah Al Hajar", -["Al_Dumayr"]="Al-Dumayr", -["Gazipasa"]="Gazipasa", -["Hatay"]="Hatay", -["Pinarbashi"]="Pinarbashi", -["Paphos"]="Paphos", -["Kingsfield"]="Kingsfield", -["Thalah"]="Tha'lah", -["Haifa"]="Haifa", -["Khalkhalah"]="Khalkhalah", -["Megiddo"]="Megiddo", -["Lakatamia"]="Lakatamia", -["Rayak"]="Rayak", -["Larnaca"]="Larnaca", -["Mezzeh"]="Mezzeh", -["Gecitkale"]="Gecitkale", -["Akrotiri"]="Akrotiri", -["Naqoura"]="Naqoura", -["Gaziantep"]="Gaziantep", -["Sayqal"]="Sayqal", -["Tiyas"]="Tiyas", -["Shayrat"]="Shayrat", -["Taftanaz"]="Taftanaz", -["H4"]="H4", -["King_Hussein_Air_College"]="King Hussein Air College", -["Rene_Mouawad"]="Rene Mouawad", -["Jirah"]="Jirah", -["Ramat_David"]="Ramat David", -["Qabr_as_Sitt"]="Qabr as Sitt", -["Minakh"]="Minakh", -["Adana_Sakirpasa"]="Adana Sakirpasa", -["Palmyra"]="Palmyra", -["Hama"]="Hama", -["Ercan"]="Ercan", -["Marj_as_Sultan_South"]="Marj as Sultan South", -["Tabqa"]="Tabqa", -["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", -["An_Nasiriyah"]="An Nasiriyah", -["Abu_al_Duhur"]="Abu al-Duhur", -["At_Tanf"]="At Tanf", -["H3"]="H3", -["H3_Northwest"]="H3 Northwest", -["H3_Southwest"]="H3 Southwest", -["Kharab_Ishk"]="Kharab Ishk", -["Ruwayshid"]="Ruwayshid", -["Sanliurfa"]="Sanliurfa", -["Tal_Siman"]="Tal Siman", -["Deir_ez_Zor"]="Deir ez-Zor", -} -AIRBASE.MarianaIslands={ -["Rota_Intl"]="Rota Intl", -["Andersen_AFB"]="Andersen AFB", -["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", -["Saipan_Intl"]="Saipan Intl", -["Tinian_Intl"]="Tinian Intl", -["Olf_Orote"]="Olf Orote", -["Pagan_Airstrip"]="Pagan Airstrip", -["North_West_Field"]="North West Field", -} -AIRBASE.SouthAtlantic={ -["Port_Stanley"]="Port Stanley", -["Mount_Pleasant"]="Mount Pleasant", -["San_Carlos_FOB"]="San Carlos FOB", -["Rio_Grande"]="Rio Grande", -["Rio_Gallegos"]="Rio Gallegos", -["Ushuaia"]="Ushuaia", -["Ushuaia_Helo_Port"]="Ushuaia Helo Port", -["Punta_Arenas"]="Punta Arenas", -["Pampa_Guanaco"]="Pampa Guanaco", -["San_Julian"]="San Julian", -["Puerto_Williams"]="Puerto Williams", -["Puerto_Natales"]="Puerto Natales", -["El_Calafate"]="El Calafate", -["Puerto_Santa_Cruz"]="Puerto Santa Cruz", -["Comandante_Luis_Piedrabuena"]="Comandante Luis Piedrabuena", -["Aerodromo_De_Tolhuin"]="Aerodromo De Tolhuin", -["Porvenir_Airfield"]="Porvenir Airfield", -["Almirante_Schroeders"]="Almirante Schroeders", -["Rio_Turbio"]="Rio Turbio", -["Rio_Chico"]="Rio Chico", -["Franco_Bianco"]="Franco Bianco", -["Goose_Green"]="Goose Green", -["Hipico_Flying_Club"]="Hipico Flying Club", -["CaletaTortel"]="CaletaTortel", -["Aeropuerto_de_Gobernador_Gregores"]="Aeropuerto de Gobernador Gregores", -["Aerodromo_O_Higgins"]="Aerodromo O'Higgins", -["Cullen_Airport"]="Cullen Airport", -["Gull_Point"]="Gull Point", -} -AIRBASE.Sinai={ -["Hatzerim"]="Hatzerim", -["Abu_Suwayr"]="Abu Suwayr", -["Sde_Dov"]="Sde Dov", -["AzZaqaziq"]="AzZaqaziq", -["Hatzor"]="Hatzor", -["Kedem"]="Kedem", -["Nevatim"]="Nevatim", -["Cairo_International_Airport"]="Cairo International Airport", -["Al_Ismailiyah"]="Al Ismailiyah", -["As_Salihiyah"]="As Salihiyah", -["Fayed"]="Fayed", -["Bilbeis_Air_Base"]="Bilbeis Air Base", -["Ramon_Airbase"]="Ramon Airbase", -["Kibrit_Air_Base"]="Kibrit Air Base", -["El_Arish"]="El Arish", -["Ovda"]="Ovda", -["Melez"]="Melez", -["Al_Mansurah"]="Al Mansurah", -["Palmahim"]="Palmahim", -["Baluza"]="Baluza", -["El_Gora"]="El Gora", -["Difarsuwar_Airfield"]="Difarsuwar Airfield", -["Wadi_al_Jandali"]="Wadi al Jandali", -["St_Catherine"]="St Catherine", -["Tel_Nof"]="Tel Nof", -["Abu_Rudeis"]="Abu Rudeis", -["Inshas_Airbase"]="Inshas Airbase", -["Ben_Gurion"]="Ben-Gurion", -["Bir_Hasanah"]="Bir Hasanah", -["Cairo_West"]="Cairo West", -} -AIRBASE.TerminalType={ -Runway=16, -HelicopterOnly=40, -Shelter=68, -OpenMed=72, -OpenBig=104, -OpenMedOrBig=176, -HelicopterUsable=216, -FighterAircraft=244, -} -AIRBASE.SpotStatus={ -FREE="Free", -OCCUPIED="Occupied", -RESERVED="Reserved", -} -function AIRBASE:Register(AirbaseName) -local self=BASE:Inherit(self,POSITIONABLE:New(AirbaseName)) -self.AirbaseName=AirbaseName -self.AirbaseID=self:GetID(true) -self.descriptors=self:GetDesc() -self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME -if self.category==Airbase.Category.AIRDROME then -self.isAirdrome=true -elseif self.category==Airbase.Category.HELIPAD then -self.isHelipad=true -elseif self.category==Airbase.Category.SHIP then -self.isShip=true -if self.descriptors.typeName=="Oil rig"or self.descriptors.typeName=="Ga"then -self.isHelipad=true -self.isShip=false -self.category=Airbase.Category.HELIPAD -_DATABASE:AddStatic(AirbaseName) -end -else -self:E("ERROR: Unknown airbase category!") -end -self:_InitRunways() -if self.isAirdrome then -self:SetActiveRunway() -end -self:_InitParkingSpots() -local vec2=self:GetVec2() -self:GetCoordinate() -self.storage=_DATABASE:AddStorage(AirbaseName) -if vec2 then -if self.isShip then -local unit=UNIT:FindByName(AirbaseName) -if unit then -self.AirbaseZone=ZONE_UNIT:New(AirbaseName,unit,2500) -end -else -self.AirbaseZone=ZONE_RADIUS:New(AirbaseName,vec2,2500) -end -else -self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s",AirbaseName)) -end -self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName))) -return self -end -function AIRBASE:Find(DCSAirbase) -local AirbaseName=DCSAirbase:getName() -local AirbaseFound=_DATABASE:FindAirbase(AirbaseName) -return AirbaseFound -end -function AIRBASE:FindByName(AirbaseName) -local AirbaseFound=_DATABASE:FindAirbase(AirbaseName) -return AirbaseFound -end -function AIRBASE:FindByID(id) -for name,_airbase in pairs(_DATABASE.AIRBASES)do -local airbase=_airbase -local aid=tonumber(airbase:GetID(true)) -if aid==id then -return airbase -end -end -return nil -end -function AIRBASE:GetDCSObject() -local DCSAirbase=Airbase.getByName(self.AirbaseName) -if DCSAirbase then -return DCSAirbase -end -return nil -end -function AIRBASE:GetZone() -return self.AirbaseZone -end -function AIRBASE:GetWarehouse() -local warehouse=nil -local airbase=self:GetDCSObject() -if airbase and Airbase.getWarehouse then -warehouse=airbase:getWarehouse() -end -return warehouse -end -function AIRBASE:GetStorage() -return self.storage -end -function AIRBASE:SetAutoCapture(Switch) -local airbase=self:GetDCSObject() -if airbase then -airbase:autoCapture(Switch) -end -return self -end -function AIRBASE:SetAutoCaptureON() -self:SetAutoCapture(true) -return self -end -function AIRBASE:SetAutoCaptureOFF() -self:SetAutoCapture(false) -return self -end -function AIRBASE:IsAutoCapture() -local airbase=self:GetDCSObject() -local auto=nil -if airbase then -auto=airbase:autoCaptureIsOn() -end -return auto -end -function AIRBASE:SetCoalition(Coal) -local airbase=self:GetDCSObject() -if airbase then -airbase:setCoalition(Coal) -end -return self -end -function AIRBASE.GetAllAirbases(coalition,category) -local airbases={} -for _,_airbase in pairs(_DATABASE.AIRBASES)do -local airbase=_airbase -if coalition==nil or airbase:GetCoalition()==coalition then -if category==nil or category==airbase:GetAirbaseCategory()then -table.insert(airbases,airbase) -end -end -end -return airbases -end -function AIRBASE.GetAllAirbaseNames(coalition,category) -local airbases={} -for airbasename,_airbase in pairs(_DATABASE.AIRBASES)do -local airbase=_airbase -if coalition==nil or airbase:GetCoalition()==coalition then -if category==nil or category==airbase:GetAirbaseCategory()then -table.insert(airbases,airbasename) -end -end -end -return airbases -end -function AIRBASE:GetID(unique) -if self.AirbaseID then -return unique and self.AirbaseID or math.abs(self.AirbaseID) -else -for DCSAirbaseId,DCSAirbase in ipairs(world.getAirbases())do -local AirbaseName=DCSAirbase:getName() -local airbaseID=tonumber(DCSAirbase:getID()) -local airbaseCategory=self:GetAirbaseCategory() -if AirbaseName==self.AirbaseName then -if airbaseCategory==Airbase.Category.SHIP or airbaseCategory==Airbase.Category.HELIPAD then -return unique and-airbaseID or airbaseID -else -return airbaseID -end -end -end -end -return nil -end -function AIRBASE:SetParkingSpotWhitelist(TerminalIdWhitelist) -if TerminalIdWhitelist==nil then -self.parkingWhitelist={} -return self -end -if type(TerminalIdWhitelist)~="table"then -TerminalIdWhitelist={TerminalIdWhitelist} -end -self.parkingWhitelist=TerminalIdWhitelist -return self -end -function AIRBASE:SetParkingSpotBlacklist(TerminalIdBlacklist) -if TerminalIdBlacklist==nil then -self.parkingBlacklist={} -return self -end -if type(TerminalIdBlacklist)~="table"then -TerminalIdBlacklist={TerminalIdBlacklist} -end -self.parkingBlacklist=TerminalIdBlacklist -return self -end -function AIRBASE:SetRadioSilentMode(Silent) -local airbase=self:GetDCSObject() -if airbase then -airbase:setRadioSilentMode(Silent) -end -return self -end -function AIRBASE:GetRadioSilentMode() -local silent=nil -local airbase=self:GetDCSObject() -if airbase then -silent=airbase:getRadioSilentMode() -end -return silent -end -function AIRBASE:GetAirbaseCategory() -return self.category -end -function AIRBASE:IsAirdrome() -return self.isAirdrome -end -function AIRBASE:IsHelipad() -return self.isHelipad -end -function AIRBASE:IsShip() -return self.isShip -end -function AIRBASE:GetParkingData(available) -self:F2(available) -local DCSAirbase=self:GetDCSObject() -local parkingdata=nil -if DCSAirbase then -parkingdata=DCSAirbase:getParking(available) -end -self:T2({parkingdata=parkingdata}) -return parkingdata -end -function AIRBASE:GetParkingSpotsNumber(termtype) -local parkingdata=self:GetParkingData(false) -local nspots=0 -for _,parkingspot in pairs(parkingdata)do -if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then -nspots=nspots+1 -end -end -return nspots -end -function AIRBASE:GetFreeParkingSpotsNumber(termtype,allowTOAC) -local parkingdata=self:GetParkingData(true) -local nfree=0 -for _,parkingspot in pairs(parkingdata)do -if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then -if(allowTOAC and allowTOAC==true)or parkingspot.TO_AC==false then -nfree=nfree+1 -end -end -end -return nfree -end -function AIRBASE:GetFreeParkingSpotsCoordinates(termtype,allowTOAC) -local parkingdata=self:GetParkingData(true) -local spots={} -for _,parkingspot in pairs(parkingdata)do -if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then -if(allowTOAC and allowTOAC==true)or parkingspot.TO_AC==false then -table.insert(spots,COORDINATE:NewFromVec3(parkingspot.vTerminalPos)) -end -end -end -return spots -end -function AIRBASE:GetParkingSpotsCoordinates(termtype) -local parkingdata=self:GetParkingData(false) -local spots={} -for _,parkingspot in ipairs(parkingdata)do -if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then -local _coord=COORDINATE:NewFromVec3(parkingspot.vTerminalPos) -table.insert(spots,_coord) -end -end -return spots -end -function AIRBASE:_InitParkingSpots() -local parkingdata=self:GetParkingData(false) -self.parking={} -self.parkingByID={} -self.NparkingTotal=0 -self.NparkingTerminal={} -for _,terminalType in pairs(AIRBASE.TerminalType)do -self.NparkingTerminal[terminalType]=0 -end -local function isClient(coord) -local clients=_DATABASE.CLIENTS -for clientname,_client in pairs(clients)do -local client=_client -if client and client.SpawnCoord then -local dist=client.SpawnCoord:Get2DDistance(coord) -if dist<2 then -return true,clientname -end -end -end -return false,nil -end -for _,spot in pairs(parkingdata)do -local park={} -park.Vec3=spot.vTerminalPos -park.Coordinate=COORDINATE:NewFromVec3(spot.vTerminalPos) -park.DistToRwy=spot.fDistToRW -park.Free=nil -park.TerminalID=spot.Term_Index -park.TerminalID0=spot.Term_Index_0 -park.TerminalType=spot.Term_Type -park.TOAC=spot.TO_AC -park.ClientSpot,park.ClientName=isClient(park.Coordinate) -park.AirbaseName=self.AirbaseName -self.NparkingTotal=self.NparkingTotal+1 -for _,terminalType in pairs(AIRBASE.TerminalType)do -if self._CheckTerminalType(terminalType,park.TerminalType)then -self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1 -end -end -self.parkingByID[park.TerminalID]=park -table.insert(self.parking,park) -end -return self -end -function AIRBASE:_GetParkingSpotByID(TerminalID) -return self.parkingByID[TerminalID] -end -function AIRBASE:GetParkingSpotsTable(termtype) -local parkingdata=self:GetParkingData(false) -local parkingfree=self:GetParkingData(true) -local function _isfree(_tocheck) -for _,_spot in pairs(parkingfree)do -if _spot.Term_Index==_tocheck.Term_Index then -return true -end -end -return false -end -local spots={} -for _,_spot in pairs(parkingdata)do -if AIRBASE._CheckTerminalType(_spot.Term_Type,termtype)then -local spot=self:_GetParkingSpotByID(_spot.Term_Index) -if spot then -spot.Free=_isfree(_spot) -spot.TOAC=_spot.TO_AC -spot.AirbaseName=self.AirbaseName -table.insert(spots,spot) -else -self:E(string.format("ERROR: Parking spot %s is nil!",tostring(_spot.Term_Index))) -end -end -end -return spots -end -function AIRBASE:GetFreeParkingSpotsTable(termtype,allowTOAC) -local parkingfree=self:GetParkingData(true) -local freespots={} -for _,_spot in pairs(parkingfree)do -if AIRBASE._CheckTerminalType(_spot.Term_Type,termtype)and _spot.Term_Index>0 then -if(allowTOAC and allowTOAC==true)or _spot.TO_AC==false then -local spot=self:_GetParkingSpotByID(_spot.Term_Index) -spot.Free=true -spot.TOAC=_spot.TO_AC -spot.AirbaseName=self.AirbaseName -table.insert(freespots,spot) -end -end -end -return freespots -end -function AIRBASE:GetParkingSpotData(TerminalID) -local parkingdata=self:GetParkingSpotsTable() -for _,_spot in pairs(parkingdata)do -local spot=_spot -self:T({TerminalID=spot.TerminalID,TerminalType=spot.TerminalType}) -if TerminalID==spot.TerminalID then -return spot -end -end -self:E("ERROR: Could not find spot with Terminal ID="..tostring(TerminalID)) -return nil -end -function AIRBASE:MarkParkingSpots(termtype,mark) -if mark==nil then -mark=true -end -local parkingdata=self:GetParkingSpotsTable(termtype) -local airbasename=self:GetName() -self:E(string.format("Parking spots at %s for terminal type %s:",airbasename,tostring(termtype))) -for _,_spot in pairs(parkingdata)do -local _text=string.format("Term Index=%d, Term Type=%d, Free=%s, TOAC=%s, Term ID0=%d, Dist2Rwy=%.1f m", -_spot.TerminalID,_spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy) -if mark then -_spot.Coordinate:MarkToAll(_text) -end -local _text=string.format("%s, Term Index=%3d, Term Type=%03d, Free=%5s, TOAC=%5s, Term ID0=%3d, Dist2Rwy=%.1f m", -airbasename,_spot.TerminalID,_spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy) -self:E(_text) -end -end -function AIRBASE:FindFreeParkingSpotForAircraft(group,terminaltype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nspots,parkingdata) -scanradius=scanradius or 50 -if scanunits==nil then -scanunits=true -end -if scanstatics==nil then -scanstatics=true -end -if scanscenery==nil then -scanscenery=false -end -if verysafe==nil then -verysafe=false -end -local function _overlap(object1,object2,dist) -local pos1=object1 -local pos2=object2 -local r1=pos1:GetBoundingRadius() -local r2=pos2:GetBoundingRadius() -if r1 and r2 then -local safedist=(r1+r2)*1.1 -local safe=(dist>safedist) -self:T2(string.format("r1=%.1f r2=%.1f s=%.1f d=%.1f ==> safe=%s",r1,r2,safedist,dist,tostring(safe))) -return safe -else -return true -end -end -local airport=self:GetName() -parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype) -local aircraft=nil -local _aircraftsize=23 -local ax=23 -local ay=7 -local az=17 -if group and group.ClassName=="GROUP"then -aircraft=group:GetUnit(1) -if aircraft then -_aircraftsize,ax,ay,az=aircraft:GetObjectSize() -end -end -local _nspots=nspots or group:GetSize() -self:T(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.",airport,_nspots,_aircraftsize,ax,ay,az,tostring(terminaltype))) -local validspots={} -local nvalid=0 -local _test=false -if _test then -return validspots -end -local markobstacles=false -for _,parkingspot in pairs(parkingdata)do -local _spot=parkingspot.Coordinate -local _termid=parkingspot.TerminalID -if AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype)and self:_CheckParkingLists(_termid)then -if verysafe and(parkingspot.Free==false or parkingspot.TOAC==true)then -self:T(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.",airport,parkingspot.TerminalID,tostring(parkingspot.Free),tostring(parkingspot.TOAC))) -else -local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius,scanunits,scanstatics,scanscenery) -local occupied=false -for _,unit in pairs(_units)do -local _coord=unit:GetCoordinate() -local _dist=_coord:Get2DDistance(_spot) -local _safe=_overlap(aircraft,unit,_dist) -if markobstacles then -local l,x,y,z=unit:GetObjectSize() -_coord:MarkToAll(string.format("Unit %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s",unit:GetName(),x,y,z,l,_dist,_termid,tostring(_safe))) -end -if scanunits and not _safe then -occupied=true -end -end -for _,static in pairs(_statics)do -local _static=STATIC:Find(static) -local _vec3=static:getPoint() -local _coord=COORDINATE:NewFromVec3(_vec3) -local _dist=_coord:Get2DDistance(_spot) -local _safe=_overlap(aircraft,_static,_dist) -if markobstacles then -local l,x,y,z=_static:GetObjectSize() -_coord:MarkToAll(string.format("Static %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s",static:getName(),x,y,z,l,_dist,_termid,tostring(_safe))) -end -if scanstatics and not _safe then -occupied=true -end -end -for _,scenery in pairs(_sceneries)do -local _scenery=SCENERY:Register(scenery:getTypeName(),scenery) -local _vec3=scenery:getPoint() -local _coord=COORDINATE:NewFromVec3(_vec3) -local _dist=_coord:Get2DDistance(_spot) -local _safe=_overlap(aircraft,_scenery,_dist) -if markobstacles then -local l,x,y,z=scenery:GetObjectSize(scenery) -_coord:MarkToAll(string.format("Scenery %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s",scenery:getTypeName(),x,y,z,l,_dist,_termid,tostring(_safe))) -end -if scanscenery and not _safe then -occupied=true -end -end -for _,_takenspot in pairs(validspots)do -local _dist=_takenspot.Coordinate:Get2DDistance(_spot) -local _safe=_overlap(aircraft,aircraft,_dist) -if not _safe then -occupied=true -end -end -if occupied then -self:T(string.format("%s: Parking spot id %d occupied.",airport,_termid)) -else -self:T(string.format("%s: Parking spot id %d free.",airport,_termid)) -if nvalid<_nspots then -table.insert(validspots,{Coordinate=_spot,TerminalID=_termid}) -end -nvalid=nvalid+1 -self:T(string.format("%s: Parking spot id %d free. Nfree=%d/%d.",airport,_termid,nvalid,_nspots)) -end -end -if nvalid>=_nspots then -return validspots -end -end -end -return validspots -end -function AIRBASE:_CheckParkingLists(TerminalID) -if self.parkingBlacklist and#self.parkingBlacklist>0 then -for _,terminalID in pairs(self.parkingBlacklist or{})do -if terminalID==TerminalID then -return false -end -end -end -if self.parkingWhitelist and#self.parkingWhitelist>0 then -for _,terminalID in pairs(self.parkingWhitelist or{})do -if terminalID==TerminalID then -return true -end -end -return false -end -return true -end -function AIRBASE._CheckTerminalType(Term_Type,termtype) -if Term_Type==nil then -return false -end -if termtype==nil then -if Term_Type==AIRBASE.TerminalType.Runway then -return false -else -return true -end -end -local match=false -if Term_Type==termtype then -match=true -end -if termtype==AIRBASE.TerminalType.OpenMedOrBig then -if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig then -match=true -end -elseif termtype==AIRBASE.TerminalType.HelicopterUsable then -if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.HelicopterOnly then -match=true -end -elseif termtype==AIRBASE.TerminalType.FighterAircraft then -if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter then -match=true -end -end -return match -end -function AIRBASE:GetRunways() -return self.runways or{} -end -function AIRBASE:GetRunwayByName(Name) -if Name==nil then -return -end -if Name then -for _,_runway in pairs(self.runways)do -local runway=_runway -local name=self:GetRunwayName(runway) -if name==Name:upper()then -return runway -end -end -end -self:E("ERROR: Could not find runway with name "..tostring(Name)) -return nil -end -function AIRBASE:_InitRunways(IncludeInverse) -if IncludeInverse==nil then -IncludeInverse=true -end -local Runways={} -if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then -self.runways={} -return{} -end -local function _createRunway(name,course,width,length,center) -local bearing=-1*course -local heading=math.deg(bearing) -local runway={} -local namefromheading=math.floor(heading/10) -if self.AirbaseName==AIRBASE.Syria.Beirut_Rafic_Hariri and math.abs(namefromheading-name)>1 then -runway.name=string.format("%02d",tonumber(namefromheading)) -else -runway.name=string.format("%02d",tonumber(name)) -end -runway.magheading=tonumber(runway.name)*10 -runway.heading=heading -runway.width=width or 0 -runway.length=length or 0 -runway.center=COORDINATE:NewFromVec3(center) -if runway.heading>360 then -runway.heading=runway.heading-360 -elseif runway.heading<0 then -runway.heading=runway.heading+360 -end -if math.abs(runway.heading-runway.magheading)>60 then -self:T(string.format("WARNING: Runway %s: heading=%.1f magheading=%.1f",runway.name,runway.heading,runway.magheading)) -runway.heading=runway.heading-180 -end -if runway.heading>360 then -runway.heading=runway.heading-360 -elseif runway.heading<0 then -runway.heading=runway.heading+360 -end -runway.position=runway.center:Translate(-runway.length/2,runway.heading) -runway.endpoint=runway.center:Translate(runway.length/2,runway.heading) -local init=runway.center:GetVec3() -local width=runway.width/2 -local L2=runway.length/2 -local offset1={x=init.x+(math.cos(bearing+math.pi)*L2),y=init.z+(math.sin(bearing+math.pi)*L2)} -local offset2={x=init.x-(math.cos(bearing+math.pi)*L2),y=init.z-(math.sin(bearing+math.pi)*L2)} -local points={} -points[1]={x=offset1.x+(math.cos(bearing+(math.pi/2))*width),y=offset1.y+(math.sin(bearing+(math.pi/2))*width)} -points[2]={x=offset1.x+(math.cos(bearing-(math.pi/2))*width),y=offset1.y+(math.sin(bearing-(math.pi/2))*width)} -points[3]={x=offset2.x+(math.cos(bearing-(math.pi/2))*width),y=offset2.y+(math.sin(bearing-(math.pi/2))*width)} -points[4]={x=offset2.x+(math.cos(bearing+(math.pi/2))*width),y=offset2.y+(math.sin(bearing+(math.pi/2))*width)} -runway.zone=ZONE_POLYGON_BASE:New(string.format("%s Runway %s",self.AirbaseName,runway.name),points) -return runway -end -local airbase=self:GetDCSObject() -if airbase then -local runways=airbase:getRunways() -self:T2(runways) -if runways then -for _,rwy in pairs(runways)do -self:T(rwy) -local runway=_createRunway(rwy.Name,rwy.course,rwy.width,rwy.length,rwy.position) -table.insert(Runways,runway) -if IncludeInverse then -local idx=tonumber(runway.name) -local name2=tostring(idx-18) -if idx<18 then -name2=tostring(idx+18) -end -local runway=_createRunway(name2,rwy.course-math.pi,rwy.width,rwy.length,rwy.position) -table.insert(Runways,runway) -end -end -end -end -local rpairs={} -for i,_ri in pairs(Runways)do -local ri=_ri -for j,_rj in pairs(Runways)do -local rj=_rj -if i0 -end -for i,j in pairs(rpairs)do -local ri=Runways[i] -local rj=Runways[j] -local c0=ri.center -local a=UTILS.VecTranslate(c0,1000,ri.heading) -local b=UTILS.VecSubstract(rj.center,ri.center) -b=UTILS.VecAdd(ri.center,b) -local left=isLeft(c0,a,b) -if left then -ri.isLeft=false -rj.isLeft=true -else -ri.isLeft=true -rj.isLeft=false -end -end -self.runways=Runways -return Runways -end -function AIRBASE:GetRunwayData(magvar,mark) -local runways={} -if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then -return{} -end -local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) -if false then -for i,_coord in pairs(runwaycoords)do -local coord=_coord -coord:Translate(100,0):MarkToAll("Runway i="..i) -end -end -magvar=magvar or UTILS.GetMagneticDeclination() -local N=#runwaycoords -local N2=N/2 -local exception=false -local name=self:GetName() -if name==AIRBASE.Nevada.Jean_Airport or -name==AIRBASE.Nevada.Creech_AFB or -name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or -name==AIRBASE.PersianGulf.Dubai_Intl or -name==AIRBASE.PersianGulf.Shiraz_International_Airport or -name==AIRBASE.PersianGulf.Kish_International_Airport or -name==AIRBASE.MarianaIslands.Andersen_AFB then -exception=1 -elseif UTILS.GetDCSMap()==DCSMAP.Syria and N>=2 and -name~=AIRBASE.Syria.Minakh and -name~=AIRBASE.Syria.Damascus and -name~=AIRBASE.Syria.Khalkhalah and -name~=AIRBASE.Syria.Marj_Ruhayyil and -name~=AIRBASE.Syria.Beirut_Rafic_Hariri then -exception=2 -end -local function f(i) -local j -if exception==1 then -j=N-(i-1) -elseif exception==2 then -if i<=N2 then -j=i+N2 -else -j=i-N2 -end -else -if i%2==0 then -j=i-1 -else -j=i+1 -end -end -if name==AIRBASE.Syria.Beirut_Rafic_Hariri then -if i==1 then -j=3 -elseif i==2 then -j=6 -elseif i==3 then -j=1 -elseif i==4 then -j=5 -elseif i==5 then -j=4 -elseif i==6 then -j=2 -end -end -if name==AIRBASE.Syria.Ramat_David then -if i==1 then -j=4 -elseif i==2 then -j=6 -elseif i==3 then -j=5 -elseif i==4 then -j=1 -elseif i==5 then -j=3 -elseif i==6 then -j=2 -end -end -return j -end -for i=1,N do -local j=f(i) -local c1=runwaycoords[i] -local c2=runwaycoords[j] -local hdg=c1:HeadingTo(c2) -local idx=string.format("%02d",UTILS.Round((hdg-magvar)/10,0)) -local runway={} -runway.heading=hdg -runway.idx=idx -runway.length=c1:Get2DDistance(c2) -runway.position=c1 -runway.endpoint=c2 -if mark then -runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m, i=%d, j=%d",runway.idx,runway.heading,magvar,runway.length,i,j)) -end -table.insert(runways,runway) -end -return runways -end -function AIRBASE:SetActiveRunway(Name,PreferLeft) -self:SetActiveRunwayTakeoff(Name,PreferLeft) -self:SetActiveRunwayLanding(Name,PreferLeft) -end -function AIRBASE:SetActiveRunwayLanding(Name,PreferLeft) -local runway=self:GetRunwayByName(Name) -if not runway then -runway=self:GetRunwayIntoWind(PreferLeft) -end -if runway then -self:T(string.format("%s: Setting active runway for landing as %s",self.AirbaseName,self:GetRunwayName(runway))) -else -self:E("ERROR: Could not set the runway for landing!") -end -self.runwayLanding=runway -return runway -end -function AIRBASE:GetActiveRunway() -return self.runwayLanding,self.runwayTakeoff -end -function AIRBASE:GetActiveRunwayLanding() -return self.runwayLanding -end -function AIRBASE:GetActiveRunwayTakeoff() -return self.runwayTakeoff -end -function AIRBASE:SetActiveRunwayTakeoff(Name,PreferLeft) -local runway=self:GetRunwayByName(Name) -if not runway then -runway=self:GetRunwayIntoWind(PreferLeft) -end -if runway then -self:T(string.format("%s: Setting active runway for takeoff as %s",self.AirbaseName,self:GetRunwayName(runway))) -else -self:E("ERROR: Could not set the runway for takeoff!") -end -self.runwayTakeoff=runway -return runway -end -function AIRBASE:GetRunwayIntoWind(PreferLeft) -local runways=self:GetRunways() -local Vwind=self:GetCoordinate():GetWindWithTurbulenceVec3() -local norm=UTILS.VecNorm(Vwind) -local iact=1 -if norm>0 then -Vwind.x=Vwind.x/norm -Vwind.y=0 -Vwind.z=Vwind.z/norm -local dotmin=nil -for i,_runway in pairs(runways)do -local runway=_runway -if PreferLeft==nil or PreferLeft==runway.isLeft then -local alpha=math.rad(runway.heading) -local Vrunway={x=math.cos(alpha),y=0,z=math.sin(alpha)} -local dot=UTILS.VecDot(Vwind,Vrunway) -if dotmin==nil or dot radius %.1f m. Despawn = %s.",self:GetName(),unit:GetName(),group:GetName(),_i,dist,radius,tostring(despawn))) -end -end -else -self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(),unit:GetName(),group:GetName())) -end -end -else -self:T(string.format("%s, checking if group %s is on runway. Group is NOT alive.",self:GetName(),group:GetName())) -end -return false -end -function AIRBASE:GetCategory() -return self.category -end -function AIRBASE:GetCategoryName() -return AIRBASE.CategoryName[self.category] -end -SCENERY={ -ClassName="SCENERY", -} -function SCENERY:Register(SceneryName,SceneryObject) -local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName)) -self.SceneryName=tostring(SceneryName) -self.SceneryObject=SceneryObject -if self.SceneryObject then -self.Life0=self.SceneryObject:getLife() -else -self.Life0=0 -end -self.Properties={} -return self -end -function SCENERY:GetProperty(PropertyName) -return self.Properties[PropertyName] -end -function SCENERY:GetAllProperties() -return self.Properties -end -function SCENERY:SetProperty(PropertyName,PropertyValue) -self.Properties[PropertyName]=PropertyValue -return self -end -function SCENERY:GetName() -return self.SceneryName -end -function SCENERY:GetDCSObject() -return self.SceneryObject -end -function SCENERY:GetLife() -local life=0 -if self.SceneryObject then -life=self.SceneryObject:getLife() -if life>self.Life0 then -self.Life0=math.floor(life*1.2) -end -end -return life -end -function SCENERY:GetLife0() -return self.Life0 or 0 -end -function SCENERY:IsAlive(Threshold) -if not Threshold then -return self:GetLife()>=1 and true or false -else -return self:GetRelativeLife()>Threshold and true or false -end -end -function SCENERY:IsDead(Threshold) -if not Threshold then -return self:GetLife()<1 and true or false -else -return self:GetRelativeLife()<=Threshold and true or false -end -end -function SCENERY:GetRelativeLife() -local life=self:GetLife() -local life0=self:GetLife0() -local rlife=math.floor((life/life0)*100) -return rlife -end -function SCENERY:GetThreatLevel() -return 0,"Scenery" -end -function SCENERY:FindByName(Name,Coordinate,Radius,Role) -local radius=Radius or 100 -local name=Name or"unknown" -local scenery=nil -local function SceneryScan(scoordinate,sradius,sname) -if scoordinate~=nil then -local Vec2=scoordinate:GetVec2() -local scanzone=ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius,true) -scanzone:Scan({Object.Category.SCENERY}) -local scanned=scanzone:GetScannedSceneryObjects() -local rscenery=nil -for _,_scenery in pairs(scanned)do -local scenery=_scenery -if tostring(scenery.SceneryName)==tostring(sname)then -rscenery=scenery -if Role then rscenery:SetProperty("ROLE",Role)end -break -end -end -return rscenery -end -return nil -end -if Coordinate then -scenery=SceneryScan(Coordinate,radius,name) -end -return scenery -end -function SCENERY:FindByNameInZone(Name,Zone,Radius) -local radius=Radius or 100 -local name=Name or"unknown" -if type(Zone)=="string"then -Zone=ZONE:FindByName(Zone) -end -local coordinate=Zone:GetCoordinate() -return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE")) -end -function SCENERY:FindByZoneName(ZoneName) -local zone=ZoneName -if type(ZoneName)=="string"then -zone=ZONE:FindByName(ZoneName) -end -local _id=zone:GetProperty('OBJECT ID') -if not _id then -BASE:E("**** Zone without object ID: "..ZoneName.." | Type: "..tostring(zone.ClassName)) -if string.find(zone.ClassName,"POLYGON")then -zone:Scan({Object.Category.SCENERY}) -local scanned=zone:GetScannedSceneryObjects() -for _,_scenery in(scanned)do -local scenery=_scenery -if scenery:IsAlive()then -local role=zone:GetProperty("ROLE") -if role then scenery:SetProperty("ROLE",role)end -return scenery -end -end -return nil -else -return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) -end -else -return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) -end -end -function SCENERY:FindAllByZoneName(ZoneName) -local zone=ZoneName -if type(ZoneName)=="string"then -zone=ZONE:FindByName(ZoneName) -end -local _id=zone:GetProperty('OBJECT ID') -if not _id then -zone:Scan({Object.Category.SCENERY}) -local scanned=zone:GetScannedSceneryObjects() -if#scanned>0 then -return scanned -else -return nil -end -else -local obj=self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) -if obj then -return{obj} -else -return nil -end -end -end -function SCENERY:Destroy() -return self -end -MARKER={ -ClassName="MARKER", -Debug=false, -lid=nil, -mid=nil, -coordinate=nil, -text=nil, -message=nil, -readonly=nil, -coalition=nil, -} -_MARKERID=0 -MARKER.version="0.1.1" -function MARKER:New(Coordinate,Text) -local self=BASE:Inherit(self,FSM:New()) -self.coordinate=UTILS.DeepCopy(Coordinate) -self.text=Text -self.readonly=false -self.message="" -_MARKERID=_MARKERID+1 -self.myid=_MARKERID -self.lid=string.format("Marker #%d | ",self.myid) -self:SetStartState("Invisible") -self:AddTransition("Invisible","Added","Visible") -self:AddTransition("Visible","Removed","Invisible") -self:AddTransition("*","Changed","*") -self:AddTransition("*","TextUpdate","*") -self:AddTransition("*","CoordUpdate","*") -self:HandleEvent(EVENTS.MarkAdded) -self:HandleEvent(EVENTS.MarkRemoved) -self:HandleEvent(EVENTS.MarkChange) -return self -end -function MARKER:ReadOnly() -self.readonly=true -return self -end -function MARKER:ReadWrite() -self.readonly=false -return self -end -function MARKER:Message(Text) -self.message=Text or"" -return self -end -function MARKER:ToAll(Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,MARKER.ToAll,self) -else -self.toall=true -self.tocoalition=nil -self.coalition=nil -self.togroup=nil -self.groupname=nil -self.groupid=nil -if self.shown then -self:Remove() -end -self.mid=UTILS.GetMarkID() -trigger.action.markToAll(self.mid,self.text,self.coordinate:GetVec3(),self.readonly,self.message) -end -return self -end -function MARKER:ToCoalition(Coalition,Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,MARKER.ToCoalition,self,Coalition) -else -self.coalition=Coalition -self.tocoalition=true -self.toall=false -self.togroup=false -self.groupname=nil -self.groupid=nil -if self.shown then -self:Remove() -end -self.mid=UTILS.GetMarkID() -trigger.action.markToCoalition(self.mid,self.text,self.coordinate:GetVec3(),self.coalition,self.readonly,self.message) -end -return self -end -function MARKER:ToBlue(Delay) -self:ToCoalition(coalition.side.BLUE,Delay) -return self -end -function MARKER:ToRed(Delay) -self:ToCoalition(coalition.side.RED,Delay) -return self -end -function MARKER:ToNeutral(Delay) -self:ToCoalition(coalition.side.NEUTRAL,Delay) -return self -end -function MARKER:ToGroup(Group,Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,MARKER.ToGroup,self,Group) -else -if Group and Group:IsAlive()~=nil then -self.groupid=Group:GetID() -if self.groupid then -self.groupname=Group:GetName() -self.togroup=true -self.tocoalition=nil -self.coalition=nil -self.toall=nil -if self.shown then -self:Remove() -end -self.mid=UTILS.GetMarkID() -trigger.action.markToGroup(self.mid,self.text,self.coordinate:GetVec3(),self.groupid,self.readonly,self.message) -end -else -end -end -return self -end -function MARKER:UpdateText(Text,Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,MARKER.UpdateText,self,Text) -else -self.text=tostring(Text) -self:Refresh() -self:TextUpdate(tostring(Text)) -end -return self -end -function MARKER:UpdateCoordinate(Coordinate,Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,MARKER.UpdateCoordinate,self,Coordinate) -else -self.coordinate=Coordinate -self:Refresh() -self:CoordUpdate(Coordinate) -end -return self -end -function MARKER:Refresh(Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,MARKER.Refresh,self) -else -if self.toall then -self:ToAll() -elseif self.tocoalition then -self:ToCoalition(self.coalition) -elseif self.togroup then -local group=GROUP:FindByName(self.groupname) -self:ToGroup(group) -else -self:E(self.lid.."ERROR: unknown To in :Refresh()!") -end -end -return self -end -function MARKER:Remove(Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,MARKER.Remove,self) -else -if self.shown then -trigger.action.removeMark(self.mid) -end -end -return self -end -function MARKER:GetCoordinate() -return self.coordinate -end -function MARKER:GetText() -return self.text -end -function MARKER:SetText(Text) -self.text=Text and tostring(Text)or"" -return self -end -function MARKER:IsVisible() -return self:Is("Visible") -end -function MARKER:IsInvisible() -return self:Is("Invisible") -end -function MARKER:OnEventMarkAdded(EventData) -if EventData and EventData.MarkID then -local MarkID=EventData.MarkID -self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s",tostring(MarkID))) -if MarkID==self.mid then -self.shown=true -self:Added(EventData) -end -end -end -function MARKER:OnEventMarkRemoved(EventData) -if EventData and EventData.MarkID then -local MarkID=EventData.MarkID -local MarkID=EventData.MarkID -self:T3(self.lid..string.format("Captured event MarkRemoved for Mark ID=%s",tostring(MarkID))) -if MarkID==self.mid then -self.shown=false -self:Removed(EventData) -end -end -end -function MARKER:OnEventMarkChange(EventData) -if EventData and EventData.MarkID then -local MarkID=EventData.MarkID -self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s",tostring(MarkID))) -if MarkID==self.mid then -local MarkID=EventData.MarkID -self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s",tostring(MarkID))) -if MarkID==self.mid then -self.text=tostring(EventData.MarkText) -self:Changed(EventData) -end -end -end -end -function MARKER:onafterAdded(From,Event,To,EventData) -local text=string.format("Captured event MarkAdded for myself:\n") -text=text..string.format("Marker ID = %s\n",tostring(EventData.MarkID)) -text=text..string.format("Coalition = %s\n",tostring(EventData.MarkCoalition)) -text=text..string.format("Group ID = %s\n",tostring(EventData.MarkGroupID)) -text=text..string.format("Initiator = %s\n",EventData.IniUnit and EventData.IniUnit:GetName()or"Nobody") -text=text..string.format("Coordinate = %s\n",EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS()or"Nowhere") -text=text..string.format("Text: \n%s",tostring(EventData.MarkText)) -self:T2(self.lid..text) -end -function MARKER:onafterRemoved(From,Event,To,EventData) -local text=string.format("Captured event MarkRemoved for myself:\n") -text=text..string.format("Marker ID = %s\n",tostring(EventData.MarkID)) -text=text..string.format("Coalition = %s\n",tostring(EventData.MarkCoalition)) -text=text..string.format("Group ID = %s\n",tostring(EventData.MarkGroupID)) -text=text..string.format("Initiator = %s\n",EventData.IniUnit and EventData.IniUnit:GetName()or"Nobody") -text=text..string.format("Coordinate = %s\n",EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS()or"Nowhere") -text=text..string.format("Text: \n%s",tostring(EventData.MarkText)) -self:T2(self.lid..text) -end -function MARKER:onafterChanged(From,Event,To,EventData) -local text=string.format("Captured event MarkChange for myself:\n") -text=text..string.format("Marker ID = %s\n",tostring(EventData.MarkID)) -text=text..string.format("Coalition = %s\n",tostring(EventData.MarkCoalition)) -text=text..string.format("Group ID = %s\n",tostring(EventData.MarkGroupID)) -text=text..string.format("Initiator = %s\n",EventData.IniUnit and EventData.IniUnit:GetName()or"Nobody") -text=text..string.format("Coordinate = %s\n",EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS()or"Nowhere") -text=text..string.format("Text: \n%s",tostring(EventData.MarkText)) -self:T2(self.lid..text) -end -function MARKER:onafterTextUpdate(From,Event,To,Text) -self:T(self.lid..string.format("New Marker Text:\n%s",Text)) -end -function MARKER:onafterCoordUpdate(From,Event,To,Coordinate) -self:T(self.lid..string.format("New Marker Coordinate in LL DMS: %s",Coordinate:ToStringLLDMS())) -end -WEAPON={ -ClassName="WEAPON", -verbose=0, -} -WEAPON.version="0.1.0" -function WEAPON:New(WeaponObject) -if WeaponObject==nil then -env.error("ERROR: Weapon object does NOT exist") -return nil -end -local self=BASE:Inherit(self,POSITIONABLE:New("Weapon")) -self.weapon=WeaponObject -self.desc=WeaponObject:getDesc() -self.category=self.desc.category -if self:IsMissile()and self.desc.missileCategory then -self.categoryMissile=self.desc.missileCategory -end -self.typeName=WeaponObject:getTypeName()or"Unknown Type" -self.name=WeaponObject:getName() -self.coalition=WeaponObject:getCoalition() -self.country=WeaponObject:getCountry() -self.launcher=WeaponObject:getLauncher() -self.launcherName="Unknown Launcher" -if self.launcher then -self.launcherName=self.launcher:getName() -self.launcherUnit=UNIT:Find(self.launcher) -end -self.coordinate=COORDINATE:NewFromVec3(self.launcher:getPoint()) -self.lid=string.format("[%s] %s | ",self.typeName,self.name) -if self.launcherUnit then -self.releaseHeading=self.launcherUnit:GetHeading() -self.releaseAltitudeASL=self.launcherUnit:GetAltitude() -self.releaseAltitudeAGL=self.launcherUnit:GetAltitude(true) -self.releaseCoordinate=self.launcherUnit:GetCoordinate() -self.releasePitch=self.launcherUnit:GetPitch() -end -self:SetTimeStepTrack() -self:SetDistanceInterceptPoint() -local text=string.format("Weapon v%s\nName=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d, Launcher=%s", -self.version,self.name,self.typeName,self.category,self.coalition,self.country,self.launcherName) -self:T(self.lid..text) -self:T2(self.desc) -return self -end -function WEAPON:SetVerbosity(VerbosityLevel) -self.verbose=VerbosityLevel or 0 -return self -end -function WEAPON:SetTimeStepTrack(TimeStep) -self.dtTrack=TimeStep or 0.01 -return self -end -function WEAPON:SetDistanceInterceptPoint(Distance) -self.distIP=Distance or 50 -return self -end -function WEAPON:SetMarkImpact(Switch) -if Switch==false then -self.impactMark=false -else -self.impactMark=true -end -return self -end -function WEAPON:SetSmokeImpact(Switch,SmokeColor) -if Switch==false then -self.impactSmoke=false -else -self.impactSmoke=true -end -self.impactSmokeColor=SmokeColor or SMOKECOLOR.Red -return self -end -function WEAPON:SetFuncTrack(FuncTrack,...) -self.trackFunc=FuncTrack -self.trackArg=arg or{} -return self -end -function WEAPON:SetFuncImpact(FuncImpact,...) -self.impactFunc=FuncImpact -self.impactArg=arg or{} -return self -end -function WEAPON:GetLauncher() -return self.launcherUnit -end -function WEAPON:GetTarget() -local target=nil -if self.weapon then -local object=self.weapon:getTarget() -if object then -local category=Object.getCategory(object) -local name=object:getName() -self:T(self.lid..string.format("Got Target Object %s, category=%d",object:getName(),category)) -if category==Object.Category.UNIT then -target=UNIT:FindByName(name) -elseif category==Object.Category.STATIC then -target=STATIC:FindByName(name,false) -elseif category==Object.Category.SCENERY then -self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!")) -else -self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!",category)) -end -end -end -return target -end -function WEAPON:GetTargetDistance(ConversionFunction) -local target=self:GetTarget() -local distance=nil -if target then -local tv3=target:GetVec3() -local wv3=self:GetVec3() -if tv3 and wv3 then -distance=UTILS.VecDist3D(tv3,wv3) -if ConversionFunction then -distance=ConversionFunction(distance) -end -end -end -return distance -end -function WEAPON:GetTargetName() -local target=self:GetTarget() -local name="None" -if target then -name=target:GetName() -end -return name -end -function WEAPON:GetVelocityVec3() -local Vvec3=nil -if self.weapon then -Vvec3=self.weapon:getVelocity() -end -return Vvec3 -end -function WEAPON:GetSpeed(ConversionFunction) -local speed=nil -if self.weapon then -local v=self:GetVelocityVec3() -speed=UTILS.VecNorm(v) -if ConversionFunction then -speed=ConversionFunction(speed) -end -end -return speed -end -function WEAPON:GetVec3() -local vec3=nil -if self.weapon then -vec3=self.weapon:getPoint() -end -return vec3 -end -function WEAPON:GetVec2() -local vec3=self:GetVec3() -if vec3 then -local vec2={x=vec3.x,y=vec3.z} -return vec2 -end -return nil -end -function WEAPON:GetTypeName() -return self.typeName -end -function WEAPON:GetCoalition() -return self.coalition -end -function WEAPON:GetCountry() -return self.country -end -function WEAPON:GetDCSObject() -return self.weapon -end -function WEAPON:GetImpactVec3() -return self.impactVec3 -end -function WEAPON:GetImpactCoordinate() -return self.impactCoord -end -function WEAPON:GetReleaseHeading(AccountForMagneticInclination) -AccountForMagneticInclination=AccountForMagneticInclination or true -if AccountForMagneticInclination then return UTILS.ClampAngle(self.releaseHeading-UTILS.GetMagneticDeclination())else return UTILS.ClampAngle(self.releaseHeading)end -end -function WEAPON:GetReleaseAltitudeASL() -return self.releaseAltitudeASL -end -function WEAPON:GetReleaseAltitudeAGL() -return self.releaseAltitudeAGL -end -function WEAPON:GetReleaseCoordinate() -return self.releaseCoordinate -end -function WEAPON:GetReleasePitch() -return self.releasePitch -end -function WEAPON:GetImpactHeading(AccountForMagneticInclination) -AccountForMagneticInclination=AccountForMagneticInclination or true -if AccountForMagneticInclination then return UTILS.ClampAngle(self.impactHeading-UTILS.GetMagneticDeclination())else return self.impactHeading end -end -function WEAPON:InAir() -local inAir=nil -if self.weapon then -inAir=self.weapon:inAir() -end -return inAir -end -function WEAPON:IsExist() -local isExist=nil -if self.weapon then -isExist=self.weapon:isExist() -end -return isExist -end -function WEAPON:IsBomb() -return self.category==Weapon.Category.BOMB -end -function WEAPON:IsMissile() -return self.category==Weapon.Category.MISSILE -end -function WEAPON:IsRocket() -return self.category==Weapon.Category.ROCKET -end -function WEAPON:IsShell() -return self.category==Weapon.Category.SHELL -end -function WEAPON:IsTorpedo() -return self.category==Weapon.Category.TORPEDO -end -function WEAPON:Destroy(Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,WEAPON.Destroy,self,0) -else -if self.weapon then -self:T(self.lid.."Destroying Weapon NOW!") -self:StopTrack() -self.weapon:destroy() -end -end -return self -end -function WEAPON:StartTrack(Delay) -Delay=math.max(Delay or 0.001,0.001) -self:T(self.lid..string.format("Start tracking weapon in %.4f sec",Delay)) -self.trackScheduleID=timer.scheduleFunction(WEAPON._TrackWeapon,self,timer.getTime()+Delay) -return self -end -function WEAPON:StopTrack(Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,WEAPON.StopTrack,self,0) -else -if self.trackScheduleID then -timer.removeFunction(self.trackScheduleID) -end -end -return self -end -function WEAPON:_TrackWeapon(time) -if self.verbose>=20 then -self:I(self.lid..string.format("Tracking at T=%.5f",time)) -end -local status,pos3=pcall( -function() -local point=self.weapon:getPosition() -return point -end -) -if status then -self.pos3=pos3 -self.vec3=UTILS.DeepCopy(self.pos3.p) -self.coordinate:UpdateFromVec3(self.vec3) -self.last_velocity=self.weapon:getVelocity() -self.tracking=true -if self.trackFunc then -self.trackFunc(self,unpack(self.trackArg)) -end -if self.verbose>=5 then -local vec2={x=self.vec3.x,y=self.vec3.z} -local height=land.getHeight(vec2) -local agl=self.vec3.y-height -local ip=self:_GetIP(self.distIP) -local d=0 -if ip then -d=UTILS.VecDist3D(self.vec3,ip) -end -self:I(self.lid..string.format("T=%.3f: Height=%.3f m AGL=%.3f m, dIP=%.3f",time,height,agl,d)) -end -else -local ip=self:_GetIP(self.distIP) -if self.verbose>=10 and ip then -self:I(self.lid.."Got intercept point!") -local coord=COORDINATE:NewFromVec3(ip) -coord:MarkToAll("Intercept point") -coord:SmokeBlue() -local d=UTILS.VecDist3D(ip,self.vec3) -self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters",d)) -end -self.impactVec3=ip or self.vec3 -self.impactCoord=COORDINATE:NewFromVec3(self.vec3) -self.impactHeading=UTILS.VecHdg(self.last_velocity) -if self.impactMark then -self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s",self.name,self.typeName,self.launcherName)) -end -if self.impactSmoke then -self.impactCoord:Smoke(self.impactSmokeColor) -end -if self.impactFunc then -self.impactFunc(self,unpack(self.impactArg or{})) -end -self.tracking=false -end -if self.tracking then -if self.dtTrack and self.dtTrack>=0.00001 then -return time+self.dtTrack -else -return nil -end -end -return nil -end -function WEAPON:_GetIP(Distance) -Distance=Distance or 50 -local ip=nil -if Distance>0 and self.pos3 then -ip=land.getIP(self.pos3.p,self.pos3.x,Distance or 20) -end -return ip -end -do -NET={ -ClassName="NET", -Version="0.1.3", -BlockTime=600, -BlockedPilots={}, -BlockedUCIDs={}, -BlockedSides={}, -BlockedSlots={}, -KnownPilots={}, -BlockMessage=nil, -UnblockMessage=nil, -lid=nil, -} -function NET:New() -local self=BASE:Inherit(self,FSM:New()) -self.BlockTime=600 -self.BlockedPilots={} -self.KnownPilots={} -self:SetBlockMessage() -self:SetUnblockMessage() -self:SetStartState("Stopped") -self:AddTransition("Stopped","Run","Running") -self:AddTransition("*","PlayerJoined","*") -self:AddTransition("*","PlayerLeft","*") -self:AddTransition("*","PlayerDied","*") -self:AddTransition("*","PlayerEjected","*") -self:AddTransition("*","PlayerBlocked","*") -self:AddTransition("*","PlayerUnblocked","*") -self:AddTransition("*","Status","*") -self:AddTransition("*","Stop","Stopped") -self.lid=string.format("NET %s | ",self.Version) -self:Run() -return self -end -function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot) -local blocked=false -local TNow=timer.getTime() -if UCID and self.BlockedUCIDs[UCID]and TNow3)then -self:__PlayerJoined(1,client,name) -self.KnownPilots[name]={ -name=name, -ucid=ucid, -id=PlayerID, -side=PlayerSide, -slot=PlayerSlot, -timestamp=TNow, -} -end -return self -end -end -if data.id==EVENTS.PlayerLeaveUnit and self.KnownPilots[name]then -self:T(self.lid.."Pilot Leaving: "..name.." | UCID: "..ucid) -self:__PlayerLeft(1,data.IniUnit,name) -self.KnownPilots[name]=false -return self -end -if data.id==EVENTS.Ejection and self.KnownPilots[name]then -self:T(self.lid.."Pilot Ejecting: "..name.." | UCID: "..ucid) -self:__PlayerEjected(1,data.IniUnit,name) -self.KnownPilots[name]=false -return self -end -if(data.id==EVENTS.PilotDead or data.id==EVENTS.SelfKillPilot or data.id==EVENTS.Crash)and self.KnownPilots[name]then -self:T(self.lid.."Pilot Dead: "..name.." | UCID: "..ucid) -self:__PlayerDied(1,data.IniUnit,name) -self.KnownPilots[name]=false -return self -end -end -return self -end -function NET:BlockPlayer(Client,PlayerName,Seconds,Message) -self:T({PlayerName,Seconds,Message}) -local name=PlayerName -if Client and(not PlayerName)then -name=Client:GetPlayerName() -elseif PlayerName then -name=PlayerName -else -self:F(self.lid.."Block: No Client or PlayerName given or nothing found!") -return self -end -local ucid=self:GetPlayerUCID(Client,name) -local addon=Seconds or self.BlockTime -self.BlockedPilots[name]=timer.getTime()+addon -self.BlockedUCIDs[ucid]=timer.getTime()+addon -local message=Message or self.BlockMessage -if name then -self:SendChatToPlayer(message,name) -else -self:SendChat(name..": "..message) -end -self:__PlayerBlocked(1,Client,name,Seconds) -local PlayerID=self:GetPlayerIDByName(name) -if PlayerID and tonumber(PlayerID)~=1 then -local outcome=net.force_player_slot(tonumber(PlayerID),0,'') -end -return self -end -function NET:BlockPlayerSet(PlayerSet,Seconds,Message) -self:T({PlayerSet.Set,Seconds,Message}) -local addon=Seconds or self.BlockTime -local message=Message or self.BlockMessage -for _,_client in pairs(PlayerSet.Set)do -local name=_client:GetPlayerName() -self:BlockPlayer(_client,name,addon,message) -end -return self -end -function NET:UnblockPlayerSet(PlayerSet,Message) -self:T({PlayerSet.Set,Seconds,Message}) -local message=Message or self.UnblockMessage -for _,_client in pairs(PlayerSet.Set)do -local name=_client:GetPlayerName() -self:UnblockPlayer(_client,name,message) -end -return self -end -function NET:BlockUCID(ucid,Seconds) -self:T({ucid,Seconds}) -local addon=Seconds or self.BlockTime -self.BlockedUCIDs[ucid]=timer.getTime()+addon -return self -end -function NET:UnblockUCID(ucid) -self:T({ucid}) -self.BlockedUCIDs[ucid]=nil -return self -end -function NET:BlockSide(Side,Seconds) -self:T({Side,Seconds}) -local addon=Seconds or self.BlockTime -if Side==1 or Side==2 then -self.BlockedSides[Side]=timer.getTime()+addon -end -return self -end -function NET:UnblockSide(Side,Seconds) -self:T({Side,Seconds}) -local addon=Seconds or self.BlockTime -if Side==1 or Side==2 then -self.BlockedSides[Side]=nil -end -return self -end -function NET:BlockSlot(Slot,Seconds) -self:T({Slot,Seconds}) -local addon=Seconds or self.BlockTime -self.BlockedSlots[Slot]=timer.getTime()+addon -return self -end -function NET:UnblockSlot(Slot) -self:T({Slot}) -self.BlockedSlots[Slot]=nil -return self -end -function NET:UnblockPlayer(Client,PlayerName,Message) -local name=PlayerName -if Client then -name=Client:GetPlayerName() -elseif PlayerName then -name=PlayerName -else -self:F(self.lid.."Unblock: No PlayerName given or not found!") -return self -end -local ucid=self:GetPlayerUCID(Client,name) -self.BlockedPilots[name]=nil -self.BlockedUCIDs[ucid]=nil -local message=Message or self.UnblockMessage -if name then -self:SendChatToPlayer(message,name) -else -self:SendChat(name..": "..message) -end -self:__PlayerUnblocked(1,Client,name) -return self -end -function NET:SetBlockMessage(Text) -self.BlockMessage=Text or"You are blocked from joining. Wait time is: "..self.BlockTime.." seconds!" -return self -end -function NET:SetBlockTime(Seconds) -self.BlockTime=Seconds or 600 -return self -end -function NET:SetUnblockMessage(Text) -self.UnblockMessage=Text or"You are unblocked now and can join again." -return self -end -function NET:SendChat(Message,ToAll) -if Message then -net.send_chat(Message,ToAll) -end -return self -end -function NET:GetPlayerIDByName(Name) -if not Name then return nil end -local playerList=net.get_player_list() -for i=1,#playerList do -local playerName=net.get_name(i) -if playerName==Name then -return playerList[i] -end -end -return nil -end -function NET:GetPlayerIDFromClient(Client) -if Client then -local name=Client:GetPlayerName() -local id=self:GetPlayerIDByName(name) -return id -else -return nil -end -end -function NET:SendChatToClient(Message,ToClient,FromClient) -local PlayerId=self:GetPlayerIDFromClient(ToClient) -local FromId=self:GetPlayerIDFromClient(FromClient) -if Message and PlayerId and FromId then -net.send_chat_to(Message,tonumber(PlayerId),tonumber(FromId)) -elseif Message and PlayerId then -net.send_chat_to(Message,tonumber(PlayerId)) -end -return self -end -function NET:SendChatToPlayer(Message,ToPlayer,FromPlayer) -local PlayerId=self:GetPlayerIDByName(ToPlayer) -local FromId=self:GetPlayerIDByName(FromPlayer) -if Message and PlayerId and FromId then -net.send_chat_to(Message,tonumber(PlayerId),tonumber(FromId)) -elseif Message and PlayerId then -net.send_chat_to(Message,tonumber(PlayerId)) -end -return self -end -function NET:LoadMission(Path) -local outcome=false -if Path then -outcome=net.load_mission(Path) -end -return outcome -end -function NET:LoadNextMission() -local outcome=false -outcome=net.load_next_mission() -return outcome -end -function NET:GetPlayerList() -local plist=nil -plist=net.get_player_list() -return plist -end -function NET:GetMyPlayerID() -return net.get_my_player_id() -end -function NET:GetServerID() -return net.get_server_id() -end -function NET:GetPlayerInfo(Client,Attribute) -local PlayerID=self:GetPlayerIDFromClient(Client) -if PlayerID then -return net.get_player_info(tonumber(PlayerID),Attribute) -else -return nil -end -end -function NET:GetPlayerUCID(Client,Name) -local PlayerID=nil -if Client then -PlayerID=self:GetPlayerIDFromClient(Client) -elseif Name then -PlayerID=self:GetPlayerIDByName(Name) -else -self:E(self.lid.."Neither client nor name provided!") -end -local ucid=net.get_player_info(tonumber(PlayerID),'ucid') -return ucid -end -function NET:Kick(Client,Message) -local PlayerID=self:GetPlayerIDFromClient(Client) -if PlayerID and tonumber(PlayerID)~=1 then -return net.kick(tonumber(PlayerID),Message) -else -return false -end -end -function NET:GetPlayerStatistic(Client,StatisticID) -local PlayerID=self:GetPlayerIDFromClient(Client) -local stats=StatisticID or 0 -if stats>7 or stats<0 then stats=0 end -if PlayerID then -return net.get_stat(tonumber(PlayerID),stats) -else -return nil -end -end -function NET:GetName(Client) -local PlayerID=self:GetPlayerIDFromClient(Client) -if PlayerID then -return net.get_name(tonumber(PlayerID)) -else -return nil -end -end -function NET:GetSlot(Client) -local PlayerID=self:GetPlayerIDFromClient(Client) -if PlayerID then -local side,slot=net.get_slot(tonumber(PlayerID)) -return side,slot -else -return nil,nil -end -end -function NET:ForceSlot(Client,SideID,SlotID) -local PlayerID=self:GetPlayerIDFromClient(Client) -if PlayerID and tonumber(PlayerID)~=1 then -return net.force_player_slot(tonumber(PlayerID),SideID,SlotID or'') -else -return false -end -end -function NET:ReturnToSpectators(Client) -local outcome=self:ForceSlot(Client,0) -return outcome -end -function NET.Lua2Json(Lua) -return net.lua2json(Lua) -end -function NET.Lua2Json(Json) -return net.json2lua(Json) -end -function NET:DoStringIn(State,DoString) -return net.dostring_in(State,DoString) -end -function NET:Log(Message) -net.log(Message) -return self -end -function NET:GetKnownPilotData(Client,Name) -local name=Name -if Client and not Name then -name=Client:GetPlayerName() -end -if name then -return self.KnownPilots[name] -else -return nil -end -end -function NET:onafterStatus(From,Event,To) -self:T({From,Event,To}) -local function HouseHold(tavolo) -local TNow=timer.getTime() -for _,entry in pairs(tavolo)do -if entry>=TNow then entry=nil end -end -end -HouseHold(self.BlockedPilots) -HouseHold(self.BlockedSides) -HouseHold(self.BlockedSlots) -HouseHold(self.BlockedUCIDs) -if self:Is("Running")then -self:__Status(-60) -end -return self -end -function NET:onafterRun(From,Event,To) -self:T({From,Event,To}) -self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler) -self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) -self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler) -self:HandleEvent(EVENTS.PilotDead,self._EventHandler) -self:HandleEvent(EVENTS.Ejection,self._EventHandler) -self:HandleEvent(EVENTS.Crash,self._EventHandler) -self:HandleEvent(EVENTS.SelfKillPilot,self._EventHandler) -self:__Status(-10) -end -function NET:onafterStop(From,Event,To) -self:T({From,Event,To}) -self:UnHandleEvent(EVENTS.PlayerEnterUnit) -self:UnHandleEvent(EVENTS.PlayerEnterAircraft) -self:UnHandleEvent(EVENTS.PlayerLeaveUnit) -self:UnHandleEvent(EVENTS.PilotDead) -self:UnHandleEvent(EVENTS.Ejection) -self:UnHandleEvent(EVENTS.Crash) -self:UnHandleEvent(EVENTS.SelfKillPilot) -return self -end -end -STORAGE={ -ClassName="STORAGE", -verbose=0, -} -STORAGE.Liquid={ -JETFUEL=0, -GASOLINE=1, -MW50=2, -DIESEL=3, -} -STORAGE.version="0.0.1" -function STORAGE:New(AirbaseName) -local self=BASE:Inherit(self,BASE:New()) -self.airbase=Airbase.getByName(AirbaseName) -if Airbase.getWarehouse then -self.warehouse=self.airbase:getWarehouse() -end -self.lid=string.format("STORAGE %s",AirbaseName) -return self -end -function STORAGE:FindByName(AirbaseName) -local storage=_DATABASE:FindStorage(AirbaseName) -return storage -end -function STORAGE:SetVerbosity(VerbosityLevel) -self.verbose=VerbosityLevel or 0 -return self -end -function STORAGE:AddItem(Name,Amount) -self:T(self.lid..string.format("Adding %d items of %s",Amount,UTILS.OneLineSerialize(Name))) -self.warehouse:addItem(Name,Amount) -return self -end -function STORAGE:SetItem(Name,Amount) -self:T(self.lid..string.format("Setting item %s to N=%d",UTILS.OneLineSerialize(Name),Amount)) -self.warehouse:setItem(Name,Amount) -return self -end -function STORAGE:GetItemAmount(Name) -local N=self.warehouse:getItemCount(Name) -return N -end -function STORAGE:RemoveItem(Name,Amount) -self:T(self.lid..string.format("Removing N=%d of item %s",Amount,Name)) -self.warehouse:removeItem(Name,Amount) -return self -end -function STORAGE:AddLiquid(Type,Amount) -self:T(self.lid..string.format("Adding %d liquids of %s",Amount,self:GetLiquidName(Type))) -self.warehouse:addLiquid(Type,Amount) -return self -end -function STORAGE:SetLiquid(Type,Amount) -self:T(self.lid..string.format("Setting liquid %s to N=%d",self:GetLiquidName(Type),Amount)) -self.warehouse:setLiquidAmount(Type,Amount) -return self -end -function STORAGE:RemoveLiquid(Type,Amount) -self:T(self.lid..string.format("Removing N=%d of liquid %s",Amount,self:GetLiquidName(Type))) -self.warehouse:removeLiquid(Type,Amount) -return self -end -function STORAGE:GetLiquidAmount(Type) -local N=self.warehouse:getLiquidAmount(Type) -return N -end -function STORAGE:GetLiquidName(Type) -local name="Unknown" -if Type==STORAGE.Liquid.JETFUEL then -name="Jet fuel" -elseif Type==STORAGE.Liquid.GASOLINE then -name="Aircraft gasoline" -elseif Type==STORAGE.Liquid.MW50 then -name="MW 50" -elseif Type==STORAGE.Liquid.DIESEL then -name="Diesel" -else -self:E(self.lid..string.format("ERROR: Unknown liquid type %s",tostring(Type))) -end -return name -end -function STORAGE:AddAmount(Type,Amount) -if type(Type)=="number"then -self:AddLiquid(Type,Amount) -else -self:AddItem(Type,Amount) -end -return self -end -function STORAGE:RemoveAmount(Type,Amount) -if type(Type)=="number"then -self:RemoveLiquid(Type,Amount) -else -self:RemoveItem(Type,Amount) -end -return self -end -function STORAGE:SetAmount(Type,Amount) -if type(Type)=="number"then -self:SetLiquid(Type,Amount) -else -self:SetItem(Type,Amount) -end -return self -end -function STORAGE:GetAmount(Type) -local N=0 -if type(Type)=="number"then -N=self:GetLiquidAmount(Type) -else -N=self:GetItemAmount(Type) -end -return N -end -function STORAGE:IsUnlimited(Type) -local N=self:GetAmount(Type) -local unlimited=false -if N>0 then -self:RemoveAmount(Type,1) -local n=self:GetAmount(Type) -unlimited=n==N -if not unlimited then -self:AddAmount(Type,1) -end -self:I(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)",tostring(Type),tostring(unlimited),N,n)) -end -return unlimited -end -function STORAGE:IsLimited(Type) -local limited=not self:IsUnlimited(Type) -return limited -end -function STORAGE:IsUnlimitedAircraft() -local unlimited=self:IsUnlimited("A-10C") -return unlimited -end -function STORAGE:IsUnlimitedLiquids() -local unlimited=self:IsUnlimited(STORAGE.Liquid.DIESEL) -return unlimited -end -function STORAGE:IsUnlimitedWeapons() -local unlimited=self:IsUnlimited(ENUMS.Storage.weapons.bombs.Mk_82) -return unlimited -end -function STORAGE:IsLimitedAircraft() -local limited=self:IsLimited("A-10C") -return limited -end -function STORAGE:IsLimitedLiquids() -local limited=self:IsLimited(STORAGE.Liquid.DIESEL) -return limited -end -function STORAGE:IsLimitedWeapons() -local limited=self:IsLimited(ENUMS.Storage.weapons.bombs.Mk_82) -return limited -end -function STORAGE:GetInventory(Item) -local inventory=self.warehouse:getInventory(Item) -return inventory.aircraft,inventory.liquids,inventory.weapon -end -CARGOS={} -do -CARGO={ -ClassName="CARGO", -Type=nil, -Name=nil, -Weight=nil, -CargoObject=nil, -CargoCarrier=nil, -Representable=false, -Slingloadable=false, -Moveable=false, -Containable=false, -Reported={}, -} -function CARGO:New(Type,Name,Weight,LoadRadius,NearRadius) -local self=BASE:Inherit(self,FSM:New()) -self:T({Type,Name,Weight,LoadRadius,NearRadius}) -self:SetStartState("UnLoaded") -self:AddTransition({"UnLoaded","Boarding"},"Board","Boarding") -self:AddTransition("Boarding","Boarding","Boarding") -self:AddTransition("Boarding","CancelBoarding","UnLoaded") -self:AddTransition("Boarding","Load","Loaded") -self:AddTransition("UnLoaded","Load","Loaded") -self:AddTransition("Loaded","UnBoard","UnBoarding") -self:AddTransition("UnBoarding","UnBoarding","UnBoarding") -self:AddTransition("UnBoarding","UnLoad","UnLoaded") -self:AddTransition("Loaded","UnLoad","UnLoaded") -self:AddTransition("*","Damaged","Damaged") -self:AddTransition("*","Destroyed","Destroyed") -self:AddTransition("*","Respawn","UnLoaded") -self:AddTransition("*","Reset","UnLoaded") -self.Type=Type -self.Name=Name -self.Weight=Weight or 0 -self.CargoObject=nil -self.CargoCarrier=nil -self.Representable=false -self.Slingloadable=false -self.Moveable=false -self.Containable=false -self.CargoLimit=0 -self.LoadRadius=LoadRadius or 500 -self:SetDeployed(false) -self.CargoScheduler=SCHEDULER:New() -CARGOS[self.Name]=self -return self -end -function CARGO:FindByName(CargoName) -local CargoFound=_DATABASE:FindCargo(CargoName) -return CargoFound -end -function CARGO:GetX() -if self:IsLoaded()then -return self.CargoCarrier:GetCoordinate().x -else -return self.CargoObject:GetCoordinate().x -end -end -function CARGO:GetY() -if self:IsLoaded()then -return self.CargoCarrier:GetCoordinate().z -else -return self.CargoObject:GetCoordinate().z -end -end -function CARGO:GetHeading() -if self:IsLoaded()then -return self.CargoCarrier:GetHeading() -else -return self.CargoObject:GetHeading() -end -end -function CARGO:CanSlingload() -return false -end -function CARGO:CanBoard() -return true -end -function CARGO:CanUnboard() -return true -end -function CARGO:CanLoad() -return true -end -function CARGO:CanUnload() -return true -end -function CARGO:Destroy() -if self.CargoObject then -self.CargoObject:Destroy() -end -self:Destroyed() -end -function CARGO:GetName() -return self.Name -end -function CARGO:GetObject() -if self:IsLoaded()then -return self.CargoCarrier -else -return self.CargoObject -end -end -function CARGO:GetObjectName() -if self:IsLoaded()then -return self.CargoCarrier:GetName() -else -return self.CargoObject:GetName() -end -end -function CARGO:GetCount() -return 1 -end -function CARGO:GetType() -return self.Type -end -function CARGO:GetTransportationMethod() -return self.TransportationMethod -end -function CARGO:GetCoalition() -if self:IsLoaded()then -return self.CargoCarrier:GetCoalition() -else -return self.CargoObject:GetCoalition() -end -end -function CARGO:GetCoordinate() -return self.CargoObject:GetCoordinate() -end -function CARGO:IsDestroyed() -return self:Is("Destroyed") -end -function CARGO:IsLoaded() -return self:Is("Loaded") -end -function CARGO:IsLoadedInCarrier(Carrier) -return self.CargoCarrier and self.CargoCarrier:GetName()==Carrier:GetName() -end -function CARGO:IsUnLoaded() -return self:Is("UnLoaded") -end -function CARGO:IsBoarding() -return self:Is("Boarding") -end -function CARGO:IsUnboarding() -return self:Is("UnBoarding") -end -function CARGO:IsAlive() -if self:IsLoaded()then -return self.CargoCarrier:IsAlive() -else -return self.CargoObject:IsAlive() -end -end -function CARGO:SetDeployed(Deployed) -self.Deployed=Deployed -end -function CARGO:IsDeployed() -return self.Deployed -end -function CARGO:Spawn(PointVec2) -self:T() -end -function CARGO:Flare(FlareColor) -if self:IsUnLoaded()then -trigger.action.signalFlare(self.CargoObject:GetVec3(),FlareColor,0) -end -end -function CARGO:FlareWhite() -self:Flare(trigger.flareColor.White) -end -function CARGO:FlareYellow() -self:Flare(trigger.flareColor.Yellow) -end -function CARGO:FlareGreen() -self:Flare(trigger.flareColor.Green) -end -function CARGO:FlareRed() -self:Flare(trigger.flareColor.Red) -end -function CARGO:Smoke(SmokeColor,Radius) -if self:IsUnLoaded()then -if Radius then -trigger.action.smoke(self.CargoObject:GetRandomVec3(Radius),SmokeColor) -else -trigger.action.smoke(self.CargoObject:GetVec3(),SmokeColor) -end -end -end -function CARGO:SmokeGreen() -self:Smoke(trigger.smokeColor.Green,Range) -end -function CARGO:SmokeRed() -self:Smoke(trigger.smokeColor.Red,Range) -end -function CARGO:SmokeWhite() -self:Smoke(trigger.smokeColor.White,Range) -end -function CARGO:SmokeOrange() -self:Smoke(trigger.smokeColor.Orange,Range) -end -function CARGO:SmokeBlue() -self:Smoke(trigger.smokeColor.Blue,Range) -end -function CARGO:SetLoadRadius(LoadRadius) -self.LoadRadius=LoadRadius or 150 -end -function CARGO:GetLoadRadius() -return self.LoadRadius -end -function CARGO:IsInLoadRadius(Coordinate) -self:T({Coordinate,LoadRadius=self.LoadRadius}) -local Distance=0 -if self:IsUnLoaded()then -local CargoCoordinate=self.CargoObject:GetCoordinate() -Distance=Coordinate:Get2DDistance(CargoCoordinate) -self:T(Distance) -if Distance<=self.LoadRadius then -return true -end -end -return false -end -function CARGO:IsInReportRadius(Coordinate) -self:T({Coordinate}) -local Distance=0 -if self:IsUnLoaded()then -Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) -self:T(Distance) -if Distance<=self.LoadRadius then -return true -end -end -return false -end -function CARGO:IsNear(Coordinate,NearRadius) -if self.CargoObject:IsAlive()then -local Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) -if Distance<=NearRadius then -return true -end -end -return false -end -function CARGO:IsInZone(Zone) -if self:IsLoaded()then -return Zone:IsPointVec2InZone(self.CargoCarrier:GetPointVec2()) -else -if self.CargoObject:GetSize()~=0 then -return Zone:IsPointVec2InZone(self.CargoObject:GetPointVec2()) -else -return false -end -end -return nil -end -function CARGO:GetPointVec2() -return self.CargoObject:GetPointVec2() -end -function CARGO:GetCoordinate() -return self.CargoObject:GetCoordinate() -end -function CARGO:GetWeight() -return self.Weight -end -function CARGO:SetWeight(Weight) -self.Weight=Weight -return self -end -function CARGO:GetVolume() -return self.Volume -end -function CARGO:SetVolume(Volume) -self.Volume=Volume -return self -end -function CARGO:MessageToGroup(Message,CarrierGroup,Name) -MESSAGE:New(Message,20,"Cargo "..self:GetName()):ToGroup(CarrierGroup) -end -function CARGO:Report(ReportText,Action,CarrierGroup) -if not self.Reported[CarrierGroup]or not self.Reported[CarrierGroup][Action]then -self.Reported[CarrierGroup]={} -self.Reported[CarrierGroup][Action]=true -self:MessageToGroup(ReportText,CarrierGroup) -if self.ReportFlareColor then -if not self.Reported[CarrierGroup]["Flaring"]then -self:Flare(self.ReportFlareColor) -self.Reported[CarrierGroup]["Flaring"]=true -end -end -if self.ReportSmokeColor then -if not self.Reported[CarrierGroup]["Smoking"]then -self:Smoke(self.ReportSmokeColor) -self.Reported[CarrierGroup]["Smoking"]=true -end -end -end -end -function CARGO:ReportFlare(FlareColor) -self.ReportFlareColor=FlareColor -end -function CARGO:ReportSmoke(SmokeColor) -self.ReportSmokeColor=SmokeColor -end -function CARGO:ReportReset(Action,CarrierGroup) -self.Reported[CarrierGroup][Action]=nil -end -function CARGO:ReportResetAll(CarrierGroup) -self.Reported[CarrierGroup]=nil -end -function CARGO:RespawnOnDestroyed(RespawnDestroyed) -if RespawnDestroyed then -self.onenterDestroyed=function(self) -self:Respawn() -end -else -self.onenterDestroyed=nil -end -end -end -do -CARGO_REPRESENTABLE={ -ClassName="CARGO_REPRESENTABLE" -} -function CARGO_REPRESENTABLE:New(CargoObject,Type,Name,LoadRadius,NearRadius) -local self=BASE:Inherit(self,CARGO:New(Type,Name,0,LoadRadius,NearRadius)) -self:T({Type,Name,LoadRadius,NearRadius}) -local Desc=CargoObject:GetDesc() -self:T({Desc=Desc}) -local Weight=math.random(80,120) -if Desc then -if Desc.typeName=="2B11 mortar"then -Weight=210 -else -Weight=Desc.massEmpty -end -end -self:SetWeight(Weight) -return self -end -function CARGO_REPRESENTABLE:Destroy() -self:T({CargoName=self:GetName()}) -return self -end -function CARGO_REPRESENTABLE:RouteTo(ToPointVec2,Speed) -self:F2(ToPointVec2) -local Points={} -local PointStartVec2=self.CargoObject:GetPointVec2() -Points[#Points+1]=PointStartVec2:WaypointGround(Speed) -Points[#Points+1]=ToPointVec2:WaypointGround(Speed) -local TaskRoute=self.CargoObject:TaskRoute(Points) -self.CargoObject:SetTask(TaskRoute,2) -return self -end -function CARGO_REPRESENTABLE:MessageToGroup(Message,TaskGroup,Name) -local CoordinateZone=ZONE_RADIUS:New("Zone",self:GetCoordinate():GetVec2(),500) -CoordinateZone:Scan({Object.Category.UNIT}) -for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do -local NearUnit=UNIT:Find(DCSUnit) -self:T({NearUnit=NearUnit}) -local NearUnitCoalition=NearUnit:GetCoalition() -local CargoCoalition=self:GetCoalition() -if NearUnitCoalition==CargoCoalition then -local Attributes=NearUnit:GetDesc() -self:T({Desc=Attributes}) -if NearUnit:HasAttribute("Trucks")then -MESSAGE:New(Message,20,NearUnit:GetCallsign().." reporting - Cargo "..self:GetName()):ToGroup(TaskGroup) -break -end -end -end -end -end -do -CARGO_REPORTABLE={ -ClassName="CARGO_REPORTABLE" -} -function CARGO_REPORTABLE:New(Type,Name,Weight,LoadRadius,NearRadius) -local self=BASE:Inherit(self,CARGO:New(Type,Name,Weight,LoadRadius,NearRadius)) -self:T({Type,Name,Weight,LoadRadius,NearRadius}) -return self -end -function CARGO_REPORTABLE:MessageToGroup(Message,TaskGroup,Name) -MESSAGE:New(Message,20,"Cargo "..self:GetName().." reporting"):ToGroup(TaskGroup) -end -end -do -CARGO_PACKAGE={ -ClassName="CARGO_PACKAGE" -} -function CARGO_PACKAGE:New(CargoCarrier,Type,Name,Weight,LoadRadius,NearRadius) -local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoCarrier,Type,Name,Weight,LoadRadius,NearRadius)) -self:T({Type,Name,Weight,LoadRadius,NearRadius}) -self:T(CargoCarrier) -self.CargoCarrier=CargoCarrier -return self -end -function CARGO_PACKAGE:onafterOnBoard(From,Event,To,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle) -self:T() -self.CargoInAir=self.CargoCarrier:InAir() -self:T(self.CargoInAir) -if not self.CargoInAir then -local Points={} -local StartPointVec2=self.CargoCarrier:GetPointVec2() -local CargoCarrierHeading=CargoCarrier:GetHeading() -local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) -self:T({CargoCarrierHeading,CargoDeployHeading}) -local CargoDeployPointVec2=CargoCarrier:GetPointVec2():Translate(BoardDistance,CargoDeployHeading) -Points[#Points+1]=StartPointVec2:WaypointGround(Speed) -Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) -local TaskRoute=self.CargoCarrier:TaskRoute(Points) -self.CargoCarrier:SetTask(TaskRoute,1) -end -self:Boarded(CargoCarrier,Speed,BoardDistance,LoadDistance,Angle) -end -function CARGO_PACKAGE:IsNear(CargoCarrier) -self:T() -local CargoCarrierPoint=CargoCarrier:GetCoordinate() -local Distance=CargoCarrierPoint:Get2DDistance(self.CargoCarrier:GetCoordinate()) -self:T(Distance) -if Distance<=self.NearRadius then -return true -else -return false -end -end -function CARGO_PACKAGE:onafterOnBoarded(From,Event,To,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle) -self:T() -if self:IsNear(CargoCarrier)then -self:__Load(1,CargoCarrier,Speed,LoadDistance,Angle) -else -self:__Boarded(1,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle) -end -end -function CARGO_PACKAGE:onafterUnBoard(From,Event,To,CargoCarrier,Speed,UnLoadDistance,UnBoardDistance,Radius,Angle) -self:T() -self.CargoInAir=self.CargoCarrier:InAir() -self:T(self.CargoInAir) -if not self.CargoInAir then -self:_Next(self.FsmP.UnLoad,UnLoadDistance,Angle) -local Points={} -local StartPointVec2=CargoCarrier:GetPointVec2() -local CargoCarrierHeading=self.CargoCarrier:GetHeading() -local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) -self:T({CargoCarrierHeading,CargoDeployHeading}) -local CargoDeployPointVec2=StartPointVec2:Translate(UnBoardDistance,CargoDeployHeading) -Points[#Points+1]=StartPointVec2:WaypointGround(Speed) -Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) -local TaskRoute=CargoCarrier:TaskRoute(Points) -CargoCarrier:SetTask(TaskRoute,1) -end -self:__UnBoarded(1,CargoCarrier,Speed) -end -function CARGO_PACKAGE:onafterUnBoarded(From,Event,To,CargoCarrier,Speed) -self:T() -if self:IsNear(CargoCarrier)then -self:__UnLoad(1,CargoCarrier,Speed) -else -self:__UnBoarded(1,CargoCarrier,Speed) -end -end -function CARGO_PACKAGE:onafterLoad(From,Event,To,CargoCarrier,Speed,LoadDistance,Angle) -self:T() -self.CargoCarrier=CargoCarrier -local StartPointVec2=self.CargoCarrier:GetPointVec2() -local CargoCarrierHeading=self.CargoCarrier:GetHeading() -local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) -local CargoDeployPointVec2=StartPointVec2:Translate(LoadDistance,CargoDeployHeading) -local Points={} -Points[#Points+1]=StartPointVec2:WaypointGround(Speed) -Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) -local TaskRoute=self.CargoCarrier:TaskRoute(Points) -self.CargoCarrier:SetTask(TaskRoute,1) -end -function CARGO_PACKAGE:onafterUnLoad(From,Event,To,CargoCarrier,Speed,Distance,Angle) -self:T() -local StartPointVec2=self.CargoCarrier:GetPointVec2() -local CargoCarrierHeading=self.CargoCarrier:GetHeading() -local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) -local CargoDeployPointVec2=StartPointVec2:Translate(Distance,CargoDeployHeading) -self.CargoCarrier=CargoCarrier -local Points={} -Points[#Points+1]=StartPointVec2:WaypointGround(Speed) -Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) -local TaskRoute=self.CargoCarrier:TaskRoute(Points) -self.CargoCarrier:SetTask(TaskRoute,1) -end -end -do -CARGO_UNIT={ -ClassName="CARGO_UNIT" -} -function CARGO_UNIT:New(CargoUnit,Type,Name,LoadRadius,NearRadius) -local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoUnit,Type,Name,LoadRadius,NearRadius)) -self:T({Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) -self.CargoObject=CargoUnit -self:SetEventPriority(5) -return self -end -function CARGO_UNIT:onenterUnBoarding(From,Event,To,ToPointVec2,NearRadius) -self:T({From,Event,To,ToPointVec2,NearRadius}) -local Angle=180 -local Speed=60 -local DeployDistance=9 -local RouteDistance=60 -if From=="Loaded"then -if not self:IsDestroyed()then -local CargoCarrier=self.CargoCarrier -if CargoCarrier:IsAlive()then -local CargoCarrierPointVec2=CargoCarrier:GetPointVec2() -local CargoCarrierHeading=self.CargoCarrier:GetHeading() -local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) -local CargoRoutePointVec2=CargoCarrierPointVec2:Translate(RouteDistance,CargoDeployHeading) -local FromDirectionVec3=CargoCarrierPointVec2:GetDirectionVec3(ToPointVec2 or CargoRoutePointVec2) -local FromAngle=CargoCarrierPointVec2:GetAngleDegrees(FromDirectionVec3) -local FromPointVec2=CargoCarrierPointVec2:Translate(DeployDistance,FromAngle) -ToPointVec2=ToPointVec2 or CargoCarrierPointVec2:GetRandomCoordinateInRadius(NearRadius,DeployDistance) -if self.CargoObject then -if CargoCarrier:IsShip()then -self.CargoObject:ReSpawnAt(ToPointVec2,CargoDeployHeading) -else -self.CargoObject:ReSpawnAt(FromPointVec2,CargoDeployHeading) -end -self:T({"CargoUnits:",self.CargoObject:GetGroup():GetName()}) -self.CargoCarrier=nil -local Points={} -Points[#Points+1]=FromPointVec2:WaypointGround(Speed,"Vee") -Points[#Points+1]=ToPointVec2:WaypointGround(Speed,"Vee") -local TaskRoute=self.CargoObject:TaskRoute(Points) -self.CargoObject:SetTask(TaskRoute,1) -self:__UnBoarding(1,ToPointVec2,NearRadius) -end -else -self:Destroyed() -end -end -end -end -function CARGO_UNIT:onleaveUnBoarding(From,Event,To,ToPointVec2,NearRadius) -self:T({From,Event,To,ToPointVec2,NearRadius}) -local Angle=180 -local Speed=10 -local Distance=5 -if From=="UnBoarding"then -return true -end -end -function CARGO_UNIT:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius) -self:T({From,Event,To,ToPointVec2,NearRadius}) -self.CargoInAir=self.CargoObject:InAir() -self:T(self.CargoInAir) -if not self.CargoInAir then -end -self:__UnLoad(1,ToPointVec2,NearRadius) -end -function CARGO_UNIT:onenterUnLoaded(From,Event,To,ToPointVec2) -self:T({ToPointVec2,From,Event,To}) -local Angle=180 -local Speed=10 -local Distance=5 -if From=="Loaded"then -local StartPointVec2=self.CargoCarrier:GetPointVec2() -local CargoCarrierHeading=self.CargoCarrier:GetHeading() -local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) -local CargoDeployCoord=StartPointVec2:Translate(Distance,CargoDeployHeading) -ToPointVec2=ToPointVec2 or COORDINATE:New(CargoDeployCoord.x,CargoDeployCoord.z) -if self.CargoObject then -self.CargoObject:ReSpawnAt(ToPointVec2,0) -self.CargoCarrier=nil -end -end -if self.OnUnLoadedCallBack then -self.OnUnLoadedCallBack(self,unpack(self.OnUnLoadedParameters)) -self.OnUnLoadedCallBack=nil -end -end -function CARGO_UNIT:onafterBoard(From,Event,To,CargoCarrier,NearRadius,...) -self:T({From,Event,To,CargoCarrier,NearRadius=NearRadius}) -self.CargoInAir=self.CargoObject:InAir() -local Desc=self.CargoObject:GetDesc() -local MaxSpeed=Desc.speedMaxOffRoad -local TypeName=Desc.typeName -if not self.CargoInAir then -local NearRadius=NearRadius or CargoCarrier:GetBoundingRadius()+5 -if self:IsNear(CargoCarrier:GetPointVec2(),NearRadius)then -self:Load(CargoCarrier,NearRadius,...) -else -if MaxSpeed and MaxSpeed==0 or TypeName and TypeName=="Stinger comm"then -self:Load(CargoCarrier,NearRadius,...) -else -local Speed=90 -local Angle=180 -local Distance=0 -local CargoCarrierPointVec2=CargoCarrier:GetPointVec2() -local CargoCarrierHeading=CargoCarrier:GetHeading() -local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) -local CargoDeployPointVec2=CargoCarrierPointVec2:Translate(Distance,CargoDeployHeading) -self.CargoObject:OptionAlarmStateGreen() -local Points={} -local PointStartVec2=self.CargoObject:GetPointVec2() -Points[#Points+1]=PointStartVec2:WaypointGround(Speed) -Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) -local TaskRoute=self.CargoObject:TaskRoute(Points) -self.CargoObject:SetTask(TaskRoute,2) -self:__Boarding(-5,CargoCarrier,NearRadius,...) -self.RunCount=0 -end -end -end -end -function CARGO_UNIT:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...) -self:T({From,Event,To,CargoCarrier:GetName(),NearRadius=NearRadius}) -self:T({IsAlive=self.CargoObject:IsAlive()}) -if CargoCarrier and CargoCarrier:IsAlive()then -if(CargoCarrier:IsAir()and not CargoCarrier:InAir())or true then -local NearRadius=NearRadius or CargoCarrier:GetBoundingRadius(NearRadius)+5 -if self:IsNear(CargoCarrier:GetPointVec2(),NearRadius)then -self:__Load(-1,CargoCarrier,...) -else -if self:IsNear(CargoCarrier:GetPointVec2(),20)then -self:__Boarding(-1,CargoCarrier,NearRadius,...) -self.RunCount=self.RunCount+1 -else -self:__Boarding(-2,CargoCarrier,NearRadius,...) -self.RunCount=self.RunCount+2 -end -if self.RunCount>=40 then -self.RunCount=0 -local Speed=90 -local Angle=180 -local Distance=0 -local CargoCarrierPointVec2=CargoCarrier:GetPointVec2() -local CargoCarrierHeading=CargoCarrier:GetHeading() -local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) -local CargoDeployPointVec2=CargoCarrierPointVec2:Translate(Distance,CargoDeployHeading) -self.CargoObject:OptionAlarmStateGreen() -local Points={} -local PointStartVec2=self.CargoObject:GetPointVec2() -Points[#Points+1]=PointStartVec2:WaypointGround(Speed,"Off road") -Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed,"Off road") -local TaskRoute=self.CargoObject:TaskRoute(Points) -self.CargoObject:SetTask(TaskRoute,0.2) -end -end -else -self.CargoObject:MessageToGroup("Cancelling Boarding... Get back on the ground!",5,CargoCarrier:GetGroup(),self:GetName()) -self:CancelBoarding(CargoCarrier,NearRadius,...) -self.CargoObject:SetCommand(self.CargoObject:CommandStopRoute(true)) -end -else -self:T("Something is wrong") -end -end -function CARGO_UNIT:onenterLoaded(From,Event,To,CargoCarrier) -self:T({From,Event,To,CargoCarrier}) -self.CargoCarrier=CargoCarrier -if self.CargoObject then -self.CargoObject:Destroy(false) -end -end -function CARGO_UNIT:GetTransportationMethod() -if self:IsLoaded()then -return"for unboarding" -else -if self:IsUnLoaded()then -return"for boarding" -else -if self:IsDeployed()then -return"delivered" -end -end -end -return"" -end -end -do -CARGO_SLINGLOAD={ -ClassName="CARGO_SLINGLOAD" -} -function CARGO_SLINGLOAD:New(CargoStatic,Type,Name,LoadRadius,NearRadius) -local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoStatic,Type,Name,nil,LoadRadius,NearRadius)) -self:T({Type,Name,NearRadius}) -self.CargoObject=CargoStatic -_EVENTDISPATCHER:CreateEventNewCargo(self) -self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead) -self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead) -self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead) -self:SetEventPriority(4) -self.NearRadius=NearRadius or 25 -return self -end -function CARGO_SLINGLOAD:OnEventCargoDead(EventData) -local Destroyed=false -if self:IsDestroyed()or self:IsUnLoaded()then -if self.CargoObject:GetName()==EventData.IniUnitName then -if not self.NoDestroy then -Destroyed=true -end -end -end -if Destroyed then -self:I({"Cargo crate destroyed: "..self.CargoObject:GetName()}) -self:Destroyed() -end -end -function CARGO_SLINGLOAD:CanSlingload() -return true -end -function CARGO_SLINGLOAD:CanBoard() -return false -end -function CARGO_SLINGLOAD:CanUnboard() -return false -end -function CARGO_SLINGLOAD:CanLoad() -return false -end -function CARGO_SLINGLOAD:CanUnload() -return false -end -function CARGO_SLINGLOAD:IsInReportRadius(Coordinate) -local Distance=0 -if self:IsUnLoaded()then -Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) -if Distance<=self.LoadRadius then -return true -end -end -return false -end -function CARGO_SLINGLOAD:IsInLoadRadius(Coordinate) -local Distance=0 -if self:IsUnLoaded()then -Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) -if Distance<=self.NearRadius then -return true -end -end -return false -end -function CARGO_SLINGLOAD:GetCoordinate() -return self.CargoObject:GetCoordinate() -end -function CARGO_SLINGLOAD:IsAlive() -local Alive=true -if self:IsLoaded()then -Alive=Alive==true and self.CargoCarrier:IsAlive() -else -Alive=Alive==true and self.CargoObject:IsAlive() -end -return Alive -end -function CARGO_SLINGLOAD:RouteTo(Coordinate) -end -function CARGO_SLINGLOAD:IsNear(CargoCarrier,NearRadius) -return self:IsNear(CargoCarrier:GetCoordinate(),NearRadius) -end -function CARGO_SLINGLOAD:Respawn() -if self.CargoObject then -self.CargoObject:ReSpawn() -self:__Reset(-0.1) -end -end -function CARGO_SLINGLOAD:onafterReset() -if self.CargoObject then -self:SetDeployed(false) -self:SetStartState("UnLoaded") -self.CargoCarrier=nil -_EVENTDISPATCHER:CreateEventNewCargo(self) -end -end -function CARGO_SLINGLOAD:GetTransportationMethod() -if self:IsLoaded()then -return"for sling loading" -else -if self:IsUnLoaded()then -return"for sling loading" -else -if self:IsDeployed()then -return"delivered" -end -end -end -return"" -end -end -do -CARGO_CRATE={ -ClassName="CARGO_CRATE" -} -function CARGO_CRATE:New(CargoStatic,Type,Name,LoadRadius,NearRadius) -local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoStatic,Type,Name,nil,LoadRadius,NearRadius)) -self:T({Type,Name,NearRadius}) -self.CargoObject=CargoStatic -_EVENTDISPATCHER:CreateEventNewCargo(self) -self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead) -self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead) -self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead) -self:SetEventPriority(4) -self.NearRadius=NearRadius or 25 -return self -end -function CARGO_CRATE:OnEventCargoDead(EventData) -local Destroyed=false -if self:IsDestroyed()or self:IsUnLoaded()or self:IsBoarding()then -if self.CargoObject:GetName()==EventData.IniUnitName then -if not self.NoDestroy then -Destroyed=true -end -end -else -if self:IsLoaded()then -local CarrierName=self.CargoCarrier:GetName() -if CarrierName==EventData.IniDCSUnitName then -MESSAGE:New("Cargo is lost from carrier "..CarrierName,15):ToAll() -Destroyed=true -self.CargoCarrier:ClearCargo() -end -end -end -if Destroyed then -self:I({"Cargo crate destroyed: "..self.CargoObject:GetName()}) -self:Destroyed() -end -end -function CARGO_CRATE:onenterUnLoaded(From,Event,To,ToPointVec2) -local Angle=180 -local Speed=10 -local Distance=10 -if From=="Loaded"then -local StartCoordinate=self.CargoCarrier:GetCoordinate() -local CargoCarrierHeading=self.CargoCarrier:GetHeading() -local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) -local CargoDeployCoord=StartCoordinate:Translate(Distance,CargoDeployHeading) -ToPointVec2=ToPointVec2 or COORDINATE:NewFromVec2({x=CargoDeployCoord.x,y=CargoDeployCoord.z}) -if self.CargoObject then -self.CargoObject:ReSpawnAt(ToPointVec2,0) -self.CargoCarrier=nil -end -end -if self.OnUnLoadedCallBack then -self.OnUnLoadedCallBack(self,unpack(self.OnUnLoadedParameters)) -self.OnUnLoadedCallBack=nil -end -end -function CARGO_CRATE:onenterLoaded(From,Event,To,CargoCarrier) -self.CargoCarrier=CargoCarrier -if self.CargoObject then -self:T("Destroying") -self.NoDestroy=true -self.CargoObject:Destroy(false) -end -end -function CARGO_CRATE:CanBoard() -return false -end -function CARGO_CRATE:CanUnboard() -return false -end -function CARGO_CRATE:CanSlingload() -return false -end -function CARGO_CRATE:IsInReportRadius(Coordinate) -local Distance=0 -if self:IsUnLoaded()then -Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) -if Distance<=self.LoadRadius then -return true -end -end -return false -end -function CARGO_CRATE:IsInLoadRadius(Coordinate) -local Distance=0 -if self:IsUnLoaded()then -Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) -if Distance<=self.NearRadius then -return true -end -end -return false -end -function CARGO_CRATE:GetCoordinate() -return self.CargoObject:GetCoordinate() -end -function CARGO_CRATE:IsAlive() -local Alive=true -if self:IsLoaded()then -Alive=Alive==true and self.CargoCarrier:IsAlive() -else -Alive=Alive==true and self.CargoObject:IsAlive() -end -return Alive -end -function CARGO_CRATE:RouteTo(Coordinate) -self:T({Coordinate=Coordinate}) -end -function CARGO_CRATE:IsNear(CargoCarrier,NearRadius) -self:T({NearRadius=NearRadius}) -return self:IsNear(CargoCarrier:GetCoordinate(),NearRadius) -end -function CARGO_CRATE:Respawn() -self:T({"Respawning crate "..self:GetName()}) -if self.CargoObject then -self.CargoObject:ReSpawn() -self:__Reset(-0.1) -end -end -function CARGO_CRATE:onafterReset() -self:T({"Reset crate "..self:GetName()}) -if self.CargoObject then -self:SetDeployed(false) -self:SetStartState("UnLoaded") -self.CargoCarrier=nil -_EVENTDISPATCHER:CreateEventNewCargo(self) -end -end -function CARGO_CRATE:GetTransportationMethod() -if self:IsLoaded()then -return"for unloading" -else -if self:IsUnLoaded()then -return"for loading" -else -if self:IsDeployed()then -return"delivered" -end -end -end -return"" -end -end -do -CARGO_GROUP={ -ClassName="CARGO_GROUP", -} -function CARGO_GROUP:New(CargoGroup,Type,Name,LoadRadius,NearRadius) -local self=BASE:Inherit(self,CARGO_REPORTABLE:New(Type,Name,0,LoadRadius,NearRadius)) -self:T({Type,Name,LoadRadius}) -self.CargoSet=SET_CARGO:New() -self.CargoGroup=CargoGroup -self.Grouped=true -self.CargoUnitTemplate={} -self.NearRadius=NearRadius -self:SetDeployed(false) -local WeightGroup=0 -local VolumeGroup=0 -self.CargoGroup:Destroy() -local GroupName=CargoGroup:GetName() -self.CargoName=Name -self.CargoTemplate=UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName)) -self.CargoTemplate.lateActivation=false -self.GroupTemplate=UTILS.DeepCopy(self.CargoTemplate) -self.GroupTemplate.name=self.CargoName.."#CARGO" -self.GroupTemplate.groupId=nil -self.GroupTemplate.units={} -for UnitID,UnitTemplate in pairs(self.CargoTemplate.units)do -UnitTemplate.name=UnitTemplate.name.."#CARGO" -local CargoUnitName=UnitTemplate.name -self.CargoUnitTemplate[CargoUnitName]=UnitTemplate -self.GroupTemplate.units[#self.GroupTemplate.units+1]=self.CargoUnitTemplate[CargoUnitName] -self.GroupTemplate.units[#self.GroupTemplate.units].unitId=nil -local Unit=UNIT:Register(CargoUnitName) -end -self.CargoGroup=GROUP:NewTemplate(self.GroupTemplate,self.GroupTemplate.CoalitionID,self.GroupTemplate.CategoryID,self.GroupTemplate.CountryID) -self.CargoObject=_DATABASE:Spawn(self.GroupTemplate) -for CargoUnitID,CargoUnit in pairs(self.CargoObject:GetUnits())do -local CargoUnitName=CargoUnit:GetName() -local Cargo=CARGO_UNIT:New(CargoUnit,Type,CargoUnitName,LoadRadius,NearRadius) -self.CargoSet:Add(CargoUnitName,Cargo) -WeightGroup=WeightGroup+Cargo:GetWeight() -end -self:SetWeight(WeightGroup) -self:T({"Weight Cargo",WeightGroup}) -_EVENTDISPATCHER:CreateEventNewCargo(self) -self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead) -self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead) -self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead) -self:SetEventPriority(4) -return self -end -function CARGO_GROUP:Respawn() -self:T({"Respawning"}) -for CargoID,CargoData in pairs(self.CargoSet:GetSet())do -local Cargo=CargoData -Cargo:Destroy() -Cargo:SetStartState("UnLoaded") -end -_DATABASE:Spawn(self.GroupTemplate) -for CargoUnitID,CargoUnit in pairs(self.CargoObject:GetUnits())do -local CargoUnitName=CargoUnit:GetName() -local Cargo=CARGO_UNIT:New(CargoUnit,self.Type,CargoUnitName,self.LoadRadius) -self.CargoSet:Add(CargoUnitName,Cargo) -end -self:SetDeployed(false) -self:SetStartState("UnLoaded") -end -function CARGO_GROUP:Ungroup() -if self.Grouped==true then -self.Grouped=false -self.CargoGroup:Destroy() -for CargoUnitName,CargoUnit in pairs(self.CargoSet:GetSet())do -local CargoUnit=CargoUnit -if CargoUnit:IsUnLoaded()then -local GroupTemplate=UTILS.DeepCopy(self.CargoTemplate) -GroupTemplate.name=self.CargoName.."#CARGO#"..CargoUnitName -GroupTemplate.groupId=nil -if CargoUnit:IsUnLoaded()then -GroupTemplate.units={} -GroupTemplate.units[1]=self.CargoUnitTemplate[CargoUnitName] -GroupTemplate.units[#GroupTemplate.units].unitId=nil -GroupTemplate.units[#GroupTemplate.units].x=CargoUnit:GetX() -GroupTemplate.units[#GroupTemplate.units].y=CargoUnit:GetY() -GroupTemplate.units[#GroupTemplate.units].heading=CargoUnit:GetHeading() -end -local CargoGroup=GROUP:NewTemplate(GroupTemplate,GroupTemplate.CoalitionID,GroupTemplate.CategoryID,GroupTemplate.CountryID) -_DATABASE:Spawn(GroupTemplate) -end -end -self.CargoObject=nil -end -end -function CARGO_GROUP:Regroup() -self:T("Regroup") -if self.Grouped==false then -self.Grouped=true -local GroupTemplate=UTILS.DeepCopy(self.CargoTemplate) -GroupTemplate.name=self.CargoName.."#CARGO" -GroupTemplate.groupId=nil -GroupTemplate.units={} -for CargoUnitName,CargoUnit in pairs(self.CargoSet:GetSet())do -local CargoUnit=CargoUnit -self:T({CargoUnit:GetName(),UnLoaded=CargoUnit:IsUnLoaded()}) -if CargoUnit:IsUnLoaded()then -CargoUnit.CargoObject:Destroy() -GroupTemplate.units[#GroupTemplate.units+1]=self.CargoUnitTemplate[CargoUnitName] -GroupTemplate.units[#GroupTemplate.units].unitId=nil -GroupTemplate.units[#GroupTemplate.units].x=CargoUnit:GetX() -GroupTemplate.units[#GroupTemplate.units].y=CargoUnit:GetY() -GroupTemplate.units[#GroupTemplate.units].heading=CargoUnit:GetHeading() -end -end -self.CargoGroup=GROUP:NewTemplate(GroupTemplate,GroupTemplate.CoalitionID,GroupTemplate.CategoryID,GroupTemplate.CountryID) -self:T({"Regroup",GroupTemplate}) -self.CargoObject=_DATABASE:Spawn(GroupTemplate) -end -end -function CARGO_GROUP:OnEventCargoDead(EventData) -self:T(EventData) -local Destroyed=false -if self:IsDestroyed()or self:IsUnLoaded()or self:IsBoarding()or self:IsUnboarding()then -Destroyed=true -for CargoID,CargoData in pairs(self.CargoSet:GetSet())do -local Cargo=CargoData -if Cargo:IsAlive()then -Destroyed=false -else -Cargo:Destroyed() -end -end -else -local CarrierName=self.CargoCarrier:GetName() -if CarrierName==EventData.IniDCSUnitName then -MESSAGE:New("Cargo is lost from carrier "..CarrierName,15):ToAll() -Destroyed=true -self.CargoCarrier:ClearCargo() -end -end -if Destroyed then -self:Destroyed() -self:T({"Cargo group destroyed"}) -end -end -function CARGO_GROUP:onafterBoard(From,Event,To,CargoCarrier,NearRadius,...) -self:T({CargoCarrier.UnitName,From,Event,To,NearRadius=NearRadius}) -NearRadius=NearRadius or self.NearRadius -self.CargoSet:ForEach( -function(Cargo,...) -self:T({"Board Unit",Cargo:GetName(),Cargo:IsDestroyed(),Cargo.CargoObject:IsAlive()}) -local CargoGroup=Cargo.CargoObject -CargoGroup:OptionAlarmStateGreen() -Cargo:__Board(1,CargoCarrier,NearRadius,...) -end,... -) -self:__Boarding(-1,CargoCarrier,NearRadius,...) -end -function CARGO_GROUP:onafterLoad(From,Event,To,CargoCarrier,...) -if From=="UnLoaded"then -for CargoID,Cargo in pairs(self.CargoSet:GetSet())do -if not Cargo:IsDestroyed()then -Cargo:Load(CargoCarrier) -end -end -end -self.CargoCarrier=CargoCarrier -self.CargoCarrier:AddCargo(self) -end -function CARGO_GROUP:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...) -local Boarded=true -local Cancelled=false -local Dead=true -self.CargoSet:Flush() -for CargoID,Cargo in pairs(self.CargoSet:GetSet())do -if not Cargo:is("Loaded") -and(not Cargo:is("Destroyed"))then -Boarded=false -end -if Cargo:is("UnLoaded")then -Cancelled=true -end -if not Cargo:is("Destroyed")then -Dead=false -end -end -if not Dead then -if not Cancelled then -if not Boarded then -self:__Boarding(-5,CargoCarrier,NearRadius,...) -else -self:T("Group Cargo is loaded") -self:__Load(1,CargoCarrier,...) -end -else -self:__CancelBoarding(1,CargoCarrier,NearRadius,...) -end -else -self:__Destroyed(1,CargoCarrier,NearRadius,...) -end -end -function CARGO_GROUP:onafterUnBoard(From,Event,To,ToPointVec2,NearRadius,...) -self:T({From,Event,To,ToPointVec2,NearRadius}) -NearRadius=NearRadius or 25 -local Timer=1 -if From=="Loaded"then -if self.CargoObject then -self.CargoObject:Destroy() -end -self.CargoSet:ForEach( -function(Cargo,NearRadius) -if not Cargo:IsDestroyed()then -local ToVec=nil -if ToPointVec2==nil then -ToVec=self.CargoCarrier:GetPointVec2():GetRandomPointVec2InRadius(2*NearRadius,NearRadius) -else -ToVec=ToPointVec2 -end -Cargo:__UnBoard(Timer,ToVec,NearRadius) -Timer=Timer+1 -end -end,{NearRadius} -) -self:__UnBoarding(1,ToPointVec2,NearRadius,...) -end -end -function CARGO_GROUP:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius,...) -local Angle=180 -local Speed=10 -local Distance=5 -if From=="UnBoarding"then -local UnBoarded=true -for CargoID,Cargo in pairs(self.CargoSet:GetSet())do -self:T({Cargo:GetName(),Cargo.current}) -if not Cargo:is("UnLoaded")and not Cargo:IsDestroyed()then -UnBoarded=false -end -end -if UnBoarded then -self:__UnLoad(1,ToPointVec2,...) -else -self:__UnBoarding(1,ToPointVec2,NearRadius,...) -end -return false -end -end -function CARGO_GROUP:onafterUnLoad(From,Event,To,ToPointVec2,...) -if From=="Loaded"then -self.CargoSet:ForEach( -function(Cargo) -local RandomVec2=nil -if ToPointVec2 then -RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20,10) -end -Cargo:UnBoard(RandomVec2) -end -) -end -self.CargoCarrier:RemoveCargo(self) -self.CargoCarrier=nil -end -function CARGO_GROUP:GetCoordinate() -local Cargo=self:GetFirstAlive() -if Cargo then -return Cargo.CargoObject:GetCoordinate() -end -return nil -end -function CARGO:GetX() -local Cargo=self:GetFirstAlive() -if Cargo then -return Cargo:GetCoordinate().x -end -return nil -end -function CARGO:GetY() -local Cargo=self:GetFirstAlive() -if Cargo then -return Cargo:GetCoordinate().z -end -return nil -end -function CARGO_GROUP:IsAlive() -local Cargo=self:GetFirstAlive() -return Cargo~=nil -end -function CARGO_GROUP:GetFirstAlive() -local CargoFirstAlive=nil -for _,Cargo in pairs(self.CargoSet:GetSet())do -if not Cargo:IsDestroyed()then -CargoFirstAlive=Cargo -break -end -end -return CargoFirstAlive -end -function CARGO_GROUP:GetCount() -return self.CargoSet:Count() -end -function CARGO_GROUP:GetGroup(Cargo) -local Cargo=Cargo or self:GetFirstAlive() -return Cargo.CargoObject:GetGroup() -end -function CARGO_GROUP:RouteTo(Coordinate) -self.CargoSet:ForEach( -function(Cargo) -Cargo.CargoObject:RouteGroundTo(Coordinate,10,"vee",0) -end -) -end -function CARGO_GROUP:IsNear(CargoCarrier,NearRadius) -self:T({NearRadius=NearRadius}) -for _,Cargo in pairs(self.CargoSet:GetSet())do -local Cargo=Cargo -if Cargo:IsAlive()then -if Cargo:IsNear(CargoCarrier:GetCoordinate(),NearRadius)then -self:T("Near") -return true -end -end -end -return nil -end -function CARGO_GROUP:IsInLoadRadius(Coordinate) -local Cargo=self:GetFirstAlive() -if Cargo then -local Distance=0 -local CargoCoordinate -if Cargo:IsLoaded()then -CargoCoordinate=Cargo.CargoCarrier:GetCoordinate() -else -CargoCoordinate=Cargo.CargoObject:GetCoordinate() -end -if CargoCoordinate then -Distance=Coordinate:Get2DDistance(CargoCoordinate) -else -return false -end -self:T({Distance=Distance,LoadRadius=self.LoadRadius}) -if Distance<=self.LoadRadius then -return true -else -return false -end -end -return nil -end -function CARGO_GROUP:IsInReportRadius(Coordinate) -local Cargo=self:GetFirstAlive() -if Cargo then -self:T({Cargo}) -local Distance=0 -if Cargo:IsUnLoaded()then -Distance=Coordinate:Get2DDistance(Cargo.CargoObject:GetCoordinate()) -if Distance<=self.LoadRadius then -return true -end -end -end -return nil -end -function CARGO_GROUP:Flare(FlareColor) -local Cargo=self.CargoSet:GetFirst() -if Cargo then -Cargo:Flare(FlareColor) -end -end -function CARGO_GROUP:Smoke(SmokeColor,Radius) -local Cargo=self.CargoSet:GetFirst() -if Cargo then -Cargo:Smoke(SmokeColor,Radius) -end -end -function CARGO_GROUP:IsInZone(Zone) -local Cargo=self.CargoSet:GetFirst() -if Cargo then -return Cargo:IsInZone(Zone) -end -return nil -end -function CARGO_GROUP:GetTransportationMethod() -if self:IsLoaded()then -return"for unboarding" -else -if self:IsUnLoaded()then -return"for boarding" -else -if self:IsDeployed()then -return"delivered" -end -end -end -return"" -end -end -SCORING={ -ClassName="SCORING", -ClassID=0, -Players={}, -AutoSave=true, -version="1.17.1" -} -local _SCORINGCoalition={ -[1]="Red", -[2]="Blue", -} -local _SCORINGCategory={ -[Unit.Category.AIRPLANE]="Plane", -[Unit.Category.HELICOPTER]="Helicopter", -[Unit.Category.GROUND_UNIT]="Vehicle", -[Unit.Category.SHIP]="Ship", -[Unit.Category.STRUCTURE]="Structure", -} -function SCORING:New(GameName) -local self=BASE:Inherit(self,BASE:New()) -if GameName then -self.GameName=GameName -else -error("A game name must be given to register the scoring results") -end -self.ScoringObjects={} -self.ScoringZones={} -self:SetMessagesToAll() -self:SetMessagesHit(false) -self:SetMessagesDestroy(true) -self:SetMessagesScore(true) -self:SetMessagesZone(true) -self:SetScaleDestroyScore(10) -self:SetScaleDestroyPenalty(30) -self:SetScoreIncrementOnHit(0) -self:SetFratricide(self.ScaleDestroyPenalty*3) -self.penaltyonfratricide=true -self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty) -self.penaltyoncoalitionchange=true -self:SetDisplayMessagePrefix() -self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) -self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) -self:HandleEvent(EVENTS.Hit,self._EventOnHit) -self:HandleEvent(EVENTS.Birth) -self:HandleEvent(EVENTS.PlayerLeaveUnit) -self.ScoringPlayerScan=BASE:ScheduleOnce(1,function() -for PlayerName,PlayerUnit in pairs(_DATABASE:GetPlayerUnits())do -self:_AddPlayerFromUnit(PlayerUnit) -self:SetScoringMenu(PlayerUnit:GetGroup()) -end -end) -self.AutoSave=true -self:OpenCSV(GameName) -return self -end -function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) -self.DisplayMessagePrefix=DisplayMessagePrefix or"" -return self -end -function SCORING:SetScaleDestroyScore(Scale) -self.ScaleDestroyScore=Scale -return self -end -function SCORING:SetScaleDestroyPenalty(Scale) -self.ScaleDestroyPenalty=Scale -return self -end -function SCORING:AddUnitScore(ScoreUnit,Score) -local UnitName=ScoreUnit:GetName() -self.ScoringObjects[UnitName]=Score -return self -end -function SCORING:RemoveUnitScore(ScoreUnit) -local UnitName=ScoreUnit:GetName() -self.ScoringObjects[UnitName]=nil -return self -end -function SCORING:AddStaticScore(ScoreStatic,Score) -local StaticName=ScoreStatic:GetName() -self.ScoringObjects[StaticName]=Score -return self -end -function SCORING:RemoveStaticScore(ScoreStatic) -local StaticName=ScoreStatic:GetName() -self.ScoringObjects[StaticName]=nil -return self -end -function SCORING:AddScoreGroup(ScoreGroup,Score) -local ScoreUnits=ScoreGroup:GetUnits() -for ScoreUnitID,ScoreUnit in pairs(ScoreUnits)do -local UnitName=ScoreUnit:GetName() -self.ScoringObjects[UnitName]=Score -end -return self -end -function SCORING:AddZoneScore(ScoreZone,Score) -local ZoneName=ScoreZone:GetName() -self.ScoringZones[ZoneName]={} -self.ScoringZones[ZoneName].ScoreZone=ScoreZone -self.ScoringZones[ZoneName].Score=Score -return self -end -function SCORING:RemoveZoneScore(ScoreZone) -local ZoneName=ScoreZone:GetName() -self.ScoringZones[ZoneName]=nil -return self -end -function SCORING:SetMessagesHit(OnOff) -self.MessagesHit=OnOff -return self -end -function SCORING:SetScoreIncrementOnHit(score) -self.ScoreIncrementOnHit=score -return self -end -function SCORING:IfMessagesHit() -return self.MessagesHit -end -function SCORING:SetMessagesDestroy(OnOff) -self.MessagesDestroy=OnOff -return self -end -function SCORING:IfMessagesDestroy() -return self.MessagesDestroy -end -function SCORING:SetMessagesScore(OnOff) -self.MessagesScore=OnOff -return self -end -function SCORING:IfMessagesScore() -return self.MessagesScore -end -function SCORING:SetMessagesZone(OnOff) -self.MessagesZone=OnOff -return self -end -function SCORING:IfMessagesZone() -return self.MessagesZone -end -function SCORING:SetMessagesToAll() -self.MessagesAudience=1 -return self -end -function SCORING:IfMessagesToAll() -return self.MessagesAudience==1 -end -function SCORING:SetMessagesToCoalition() -self.MessagesAudience=2 -return self -end -function SCORING:IfMessagesToCoalition() -return self.MessagesAudience==2 -end -function SCORING:SetFratricide(Fratricide) -self.Fratricide=Fratricide -return self -end -function SCORING:SwitchFratricide(OnOff) -self.penaltyonfratricide=OnOff -return self -end -function SCORING:SwitchTreason(OnOff) -self.penaltyoncoalitionchange=OnOff -return self -end -function SCORING:SetCoalitionChangePenalty(CoalitionChangePenalty) -self.CoalitionChangePenalty=CoalitionChangePenalty -return self -end -function SCORING:SetScoringMenu(ScoringGroup) -local Menu=MENU_GROUP:New(ScoringGroup,'Scoring and Statistics') -local ReportGroupSummary=MENU_GROUP_COMMAND:New(ScoringGroup,'Summary report players in group',Menu,SCORING.ReportScoreGroupSummary,self,ScoringGroup) -local ReportGroupDetailed=MENU_GROUP_COMMAND:New(ScoringGroup,'Detailed report players in group',Menu,SCORING.ReportScoreGroupDetailed,self,ScoringGroup) -local ReportToAllSummary=MENU_GROUP_COMMAND:New(ScoringGroup,'Summary report all players',Menu,SCORING.ReportScoreAllSummary,self,ScoringGroup) -self:SetState(ScoringGroup,"ScoringMenu",Menu) -return self -end -function SCORING:_AddPlayerFromUnit(UnitData) -self:F(UnitData) -if UnitData:IsAlive()then -local UnitName=UnitData:GetName() -local PlayerName=UnitData:GetPlayerName() -local UnitDesc=UnitData:GetDesc() -local UnitCategory=UnitDesc.category -local UnitCoalition=UnitData:GetCoalition() -local UnitTypeName=UnitData:GetTypeName() -local UnitThreatLevel,UnitThreatType=UnitData:GetThreatLevel() -self:T({PlayerName,UnitName,UnitCategory,UnitCoalition,UnitTypeName}) -if self.Players[PlayerName]==nil then -self.Players[PlayerName]={} -self.Players[PlayerName].Hit={} -self.Players[PlayerName].Destroy={} -self.Players[PlayerName].Goals={} -self.Players[PlayerName].Mission={} -self.Players[PlayerName].HitPlayers={} -self.Players[PlayerName].Score=0 -self.Players[PlayerName].Penalty=0 -self.Players[PlayerName].PenaltyCoalition=0 -self.Players[PlayerName].PenaltyWarning=0 -end -if not self.Players[PlayerName].UnitCoalition then -self.Players[PlayerName].UnitCoalition=UnitCoalition -else -if self.Players[PlayerName].UnitCoalition~=UnitCoalition and self.penaltyoncoalitionchange then -self.Players[PlayerName].Penalty=self.Players[PlayerName].Penalty+self.CoalitionChangePenalty or 50 -self.Players[PlayerName].PenaltyCoalition=self.Players[PlayerName].PenaltyCoalition+1 -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' changed coalition from ".._SCORINGCoalition[self.Players[PlayerName].UnitCoalition].." to ".._SCORINGCoalition[UnitCoalition].. -"(changed "..self.Players[PlayerName].PenaltyCoalition.." times the coalition). "..self.CoalitionChangePenalty.." penalty points added.", -MESSAGE.Type.Information -):ToAll() -self:ScoreCSV(PlayerName,"","COALITION_PENALTY",1,-1*self.CoalitionChangePenalty,self.Players[PlayerName].UnitName,_SCORINGCoalition[self.Players[PlayerName].UnitCoalition],_SCORINGCategory[self.Players[PlayerName].UnitCategory],self.Players[PlayerName].UnitType, -UnitName,_SCORINGCoalition[UnitCoalition],_SCORINGCategory[UnitCategory],UnitData:GetTypeName()) -end -end -self.Players[PlayerName].UnitName=UnitName -self.Players[PlayerName].UnitCoalition=UnitCoalition -self.Players[PlayerName].UnitCategory=UnitCategory -self.Players[PlayerName].UnitType=UnitTypeName -self.Players[PlayerName].UNIT=UnitData -self.Players[PlayerName].ThreatLevel=UnitThreatLevel -self.Players[PlayerName].ThreatType=UnitThreatType -if self.Players[PlayerName].Penalty>self.Fratricide*0.50 and self.penaltyonfratricide then -if self.Players[PlayerName].PenaltyWarning<1 then -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than "..self.Fratricide..", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: "..self.Players[PlayerName].Penalty, -MESSAGE.Type.Information) -:ToAll() -self.Players[PlayerName].PenaltyWarning=self.Players[PlayerName].PenaltyWarning+1 -end -end -if self.Players[PlayerName].Penalty>self.Fratricide and self.penaltyonfratricide then -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!", -MESSAGE.Type.Information) -:ToAll() -UnitData:GetGroup():Destroy() -end -end -end -function SCORING:AddGoalScorePlayer(PlayerName,GoalTag,Text,Score) -self:F({PlayerName,PlayerName,GoalTag,Text,Score}) -if PlayerName then -local PlayerData=self.Players[PlayerName] -PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0} -PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score -PlayerData.Score=PlayerData.Score+Score -if Text then -MESSAGE:NewType(self.DisplayMessagePrefix..Text, -MESSAGE.Type.Information) -:ToAll() -end -self:ScoreCSV(PlayerName,"","GOAL_"..string.upper(GoalTag),1,Score,nil) -end -end -function SCORING:AddGoalScore(PlayerUnit,GoalTag,Text,Score) -local PlayerName=PlayerUnit:GetPlayerName() -self:T2({PlayerUnit.UnitName,PlayerName,GoalTag,Text,Score}) -if PlayerName then -local PlayerData=self.Players[PlayerName] -PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0} -PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score -PlayerData.Score=PlayerData.Score+Score -if Text then -MESSAGE:NewType(self.DisplayMessagePrefix..Text, -MESSAGE.Type.Information) -:ToAll() -end -self:ScoreCSV(PlayerName,"","GOAL_"..string.upper(GoalTag),1,Score,PlayerUnit:GetName()) -end -end -function SCORING:_AddMissionTaskScore(Mission,PlayerUnit,Text,Score) -local PlayerName=PlayerUnit:GetPlayerName() -local MissionName=Mission:GetName() -self:F({Mission:GetName(),PlayerUnit.UnitName,PlayerName,Text,Score}) -if PlayerName then -local PlayerData=self.Players[PlayerName] -if not PlayerData.Mission[MissionName]then -PlayerData.Mission[MissionName]={} -PlayerData.Mission[MissionName].ScoreTask=0 -PlayerData.Mission[MissionName].ScoreMission=0 -end -self:T(PlayerName) -self:T(PlayerData.Mission[MissionName]) -PlayerData.Score=self.Players[PlayerName].Score+Score -PlayerData.Mission[MissionName].ScoreTask=self.Players[PlayerName].Mission[MissionName].ScoreTask+Score -if Text then -MESSAGE:NewType(self.DisplayMessagePrefix..Mission:GetText().." : "..Text.." Score: "..Score, -MESSAGE.Type.Information) -:ToAll() -end -self:ScoreCSV(PlayerName,"","TASK_"..MissionName:gsub(' ','_'),1,Score,PlayerUnit:GetName()) -end -end -function SCORING:_AddMissionGoalScore(Mission,PlayerName,Text,Score) -local MissionName=Mission:GetName() -self:F({Mission:GetName(),PlayerName,Text,Score}) -if PlayerName then -local PlayerData=self.Players[PlayerName] -if not PlayerData.Mission[MissionName]then -PlayerData.Mission[MissionName]={} -PlayerData.Mission[MissionName].ScoreTask=0 -PlayerData.Mission[MissionName].ScoreMission=0 -end -self:T(PlayerName) -self:T(PlayerData.Mission[MissionName]) -PlayerData.Score=self.Players[PlayerName].Score+Score -PlayerData.Mission[MissionName].ScoreTask=self.Players[PlayerName].Mission[MissionName].ScoreTask+Score -if Text then -MESSAGE:NewType(string.format("%s%s: %s! Player %s receives %d score!",self.DisplayMessagePrefix,Mission:GetText(),Text,PlayerName,Score),MESSAGE.Type.Information):ToAll() -end -self:ScoreCSV(PlayerName,"","TASK_"..MissionName:gsub(' ','_'),1,Score) -end -end -function SCORING:_AddMissionScore(Mission,Text,Score) -local MissionName=Mission:GetName() -self:F({Mission,Text,Score}) -self:F(self.Players) -for PlayerName,PlayerData in pairs(self.Players)do -self:F(PlayerData) -if PlayerData.Mission[MissionName]then -PlayerData.Score=PlayerData.Score+Score -PlayerData.Mission[MissionName].ScoreMission=PlayerData.Mission[MissionName].ScoreMission+Score -if Text then -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' has "..Text.." in "..Mission:GetText()..". "..Score.." mission score!", -MESSAGE.Type.Information) -:ToAll() -end -self:ScoreCSV(PlayerName,"","MISSION_"..MissionName:gsub(' ','_'),1,Score) -end -end -end -function SCORING:OnEventBirth(Event) -if Event.IniUnit then -Event.IniUnit.ThreatLevel,Event.IniUnit.ThreatType=Event.IniUnit:GetThreatLevel() -if Event.IniObjectCategory==1 then -local PlayerName=Event.IniUnit:GetPlayerName() -Event.IniUnit.BirthTime=timer.getTime() -if PlayerName then -self:_AddPlayerFromUnit(Event.IniUnit) -self.Players[PlayerName].PlayerKills=0 -self:SetScoringMenu(Event.IniGroup) -end -end -end -end -function SCORING:OnEventPlayerLeaveUnit(Event) -if Event.IniUnit then -local Menu=self:GetState(Event.IniUnit:GetGroup(),"ScoringMenu") -if Menu then -end -end -end -function SCORING:_EventOnHit(Event) -self:F({Event}) -local InitUnit=nil -local InitUNIT=nil -local InitUnitName="" -local InitGroup=nil -local InitGroupName="" -local InitPlayerName=nil -local InitCoalition=nil -local InitCategory=nil -local InitType=nil -local InitUnitCoalition=nil -local InitUnitCategory=nil -local InitUnitType=nil -local TargetUnit=nil -local TargetUNIT=nil -local TargetUnitName="" -local TargetGroup=nil -local TargetGroupName="" -local TargetPlayerName=nil -local TargetCoalition=nil -local TargetCategory=nil -local TargetType=nil -local TargetUnitCoalition=nil -local TargetUnitCategory=nil -local TargetUnitType=nil -if Event.IniDCSUnit then -InitUnit=Event.IniDCSUnit -InitUNIT=Event.IniUnit -InitUnitName=Event.IniDCSUnitName -InitGroup=Event.IniDCSGroup -InitGroupName=Event.IniDCSGroupName -InitPlayerName=Event.IniPlayerName -InitCoalition=Event.IniCoalition -InitCategory=Event.IniCategory -InitType=Event.IniTypeName -InitUnitCoalition=_SCORINGCoalition[InitCoalition] -InitUnitCategory=_SCORINGCategory[InitCategory] -InitUnitType=InitType -self:T({InitUnitName,InitGroupName,InitPlayerName,InitCoalition,InitCategory,InitType,InitUnitCoalition,InitUnitCategory,InitUnitType}) -end -if Event.TgtDCSUnit then -TargetUnit=Event.TgtDCSUnit -TargetUNIT=Event.TgtUnit -TargetUnitName=Event.TgtDCSUnitName -TargetGroup=Event.TgtDCSGroup -TargetGroupName=Event.TgtDCSGroupName -TargetPlayerName=Event.TgtPlayerName -TargetCoalition=Event.TgtCoalition -TargetCategory=Event.TgtCategory -TargetType=Event.TgtTypeName -TargetUnitCoalition=_SCORINGCoalition[TargetCoalition] -TargetUnitCategory=_SCORINGCategory[TargetCategory] -TargetUnitType=TargetType -self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType,TargetUnitCoalition,TargetUnitCategory,TargetUnitType}) -end -if InitPlayerName~=nil then -self:_AddPlayerFromUnit(InitUNIT) -if self.Players[InitPlayerName]then -if TargetPlayerName~=nil then -self:_AddPlayerFromUnit(TargetUNIT) -end -self:T("Hitting Something") -if TargetCategory then -local Player=self.Players[InitPlayerName] -Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} -Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} -local PlayerHit=Player.Hit[TargetCategory][TargetUnitName] -PlayerHit.Score=PlayerHit.Score or 0 -PlayerHit.Penalty=PlayerHit.Penalty or 0 -PlayerHit.ScoreHit=PlayerHit.ScoreHit or 0 -PlayerHit.PenaltyHit=PlayerHit.PenaltyHit or 0 -PlayerHit.TimeStamp=PlayerHit.TimeStamp or 0 -PlayerHit.UNIT=PlayerHit.UNIT or TargetUNIT -if PlayerHit.UNIT.ThreatType==nil then -PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() -if PlayerHit.ThreatType==nil then -PlayerHit.ThreatLevel=1 -PlayerHit.ThreatType="Unknown" -end -else -PlayerHit.ThreatLevel=PlayerHit.UNIT.ThreatLevel -PlayerHit.ThreatType=PlayerHit.UNIT.ThreatType -end -if timer.getTime()-PlayerHit.TimeStamp>1 then -PlayerHit.TimeStamp=timer.getTime() -if TargetPlayerName~=nil then -Player.HitPlayers[TargetPlayerName]=true -end -local Score=0 -if InitCoalition then -if InitCoalition==TargetCoalition then -local Penalty=10 -Player.Penalty=Player.Penalty+Penalty -PlayerHit.Penalty=PlayerHit.Penalty+Penalty -PlayerHit.PenaltyHit=PlayerHit.PenaltyHit+1 -if TargetPlayerName~=nil then -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit friendly player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.PenaltyHit.." times. ".. -"Penalty: -"..Penalty..". Score Total:"..Player.Score-Player.Penalty, -MESSAGE.Type.Update) -:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) -else -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit friendly target "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.PenaltyHit.." times. ".. -"Penalty: -"..Penalty..". Score Total:"..Player.Score-Player.Penalty, -MESSAGE.Type.Update) -:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) -end -self:ScoreCSV(InitPlayerName,TargetPlayerName,"HIT_PENALTY",1,-10,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) -else -Player.Score=Player.Score+self.ScoreIncrementOnHit -PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit -PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 -if TargetPlayerName~=nil then -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit enemy player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.ScoreHit.." times. ".. -"Score: "..PlayerHit.Score..". Score Total:"..Player.Score-Player.Penalty, -MESSAGE.Type.Update) -:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) -else -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit enemy target "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.ScoreHit.." times. ".. -"Score: "..PlayerHit.Score..". Score Total:"..Player.Score-Player.Penalty, -MESSAGE.Type.Update) -:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) -end -self:ScoreCSV(InitPlayerName,TargetPlayerName,"HIT_SCORE",1,1,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) -end -else -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit scenery object.", -MESSAGE.Type.Update) -:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) -self:ScoreCSV(InitPlayerName,"","HIT_SCORE",1,0,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Scenery",TargetUnitType) -end -end -end -end -elseif InitPlayerName==nil then -end -if Event.WeaponPlayerName~=nil then -self:_AddPlayerFromUnit(Event.WeaponUNIT) -if self.Players[Event.WeaponPlayerName]then -if TargetPlayerName~=nil then -self:_AddPlayerFromUnit(TargetUNIT) -end -self:T("Hitting Scenery") -if TargetCategory then -local Player=self.Players[Event.WeaponPlayerName] -Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} -Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} -local PlayerHit=Player.Hit[TargetCategory][TargetUnitName] -PlayerHit.Score=PlayerHit.Score or 0 -PlayerHit.Penalty=PlayerHit.Penalty or 0 -PlayerHit.ScoreHit=PlayerHit.ScoreHit or 0 -PlayerHit.PenaltyHit=PlayerHit.PenaltyHit or 0 -PlayerHit.TimeStamp=PlayerHit.TimeStamp or 0 -PlayerHit.UNIT=PlayerHit.UNIT or TargetUNIT -if PlayerHit.UNIT.ThreatType==nil then -PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() -if PlayerHit.ThreatType==nil then -PlayerHit.ThreatLevel=1 -PlayerHit.ThreatType="Unknown" -end -else -PlayerHit.ThreatLevel=PlayerHit.UNIT.ThreatLevel -PlayerHit.ThreatType=PlayerHit.UNIT.ThreatType -end -if timer.getTime()-PlayerHit.TimeStamp>1 then -PlayerHit.TimeStamp=timer.getTime() -local Score=0 -if InitCoalition then -if InitCoalition==TargetCoalition then -local Penalty=10 -Player.Penalty=Player.Penalty+Penalty -PlayerHit.Penalty=PlayerHit.Penalty+Penalty -PlayerHit.PenaltyHit=PlayerHit.PenaltyHit+1*self.ScaleDestroyPenalty -MESSAGE -:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit friendly target ".. -TargetUnitCategory.." ( "..TargetType.." ) ".. -"Penalty: -"..Penalty.." = "..Player.Score-Player.Penalty, -MESSAGE.Type.Update -) -:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) -:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) -self:ScoreCSV(Event.WeaponPlayerName,TargetPlayerName,"HIT_PENALTY",1,-10,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) -else -Player.Score=Player.Score+self.ScoreIncrementOnHit -PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit -PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit enemy target "..TargetUnitCategory.." ( "..TargetType.." ) ".. -"Score: "..PlayerHit.Score..". Score Total:"..Player.Score-Player.Penalty, -MESSAGE.Type.Update) -:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) -:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) -self:ScoreCSV(Event.WeaponPlayerName,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) -end -else -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit scenery object.", -MESSAGE.Type.Update) -:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) -self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType) -end -end -end -end -end -end -function SCORING:_EventOnDeadOrCrash(Event) -self:F({Event}) -local TargetUnit=nil -local TargetGroup=nil -local TargetUnitName="" -local TargetGroupName="" -local TargetPlayerName="" -local TargetCoalition=nil -local TargetCategory=nil -local TargetType=nil -local TargetUnitCoalition=nil -local TargetUnitCategory=nil -local TargetUnitType=nil -if Event.IniDCSUnit then -TargetUnit=Event.IniUnit -TargetUnitName=Event.IniDCSUnitName -TargetGroup=Event.IniDCSGroup -TargetGroupName=Event.IniDCSGroupName -TargetPlayerName=Event.IniPlayerName -TargetCoalition=Event.IniCoalition -TargetCategory=Event.IniCategory -TargetType=Event.IniTypeName -TargetUnitCoalition=_SCORINGCoalition[TargetCoalition] -TargetUnitCategory=_SCORINGCategory[TargetCategory] -TargetUnitType=TargetType -self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType}) -end -for PlayerName,Player in pairs(self.Players)do -if Player then -self:T("Something got destroyed") -local InitUnitName=Player.UnitName -local InitUnitType=Player.UnitType -local InitCoalition=Player.UnitCoalition -local InitCategory=Player.UnitCategory -local InitUnitCoalition=_SCORINGCoalition[InitCoalition] -local InitUnitCategory=_SCORINGCategory[InitCategory] -self:T({InitUnitName,InitUnitType,InitUnitCoalition,InitCoalition,InitUnitCategory,InitCategory}) -local Destroyed=false -if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 and(TargetUnit.BirthTime==nil or Player.Hit[TargetCategory][TargetUnitName].TimeStamp>TargetUnit.BirthTime)then -local TargetThreatLevel=Player.Hit[TargetCategory][TargetUnitName].ThreatLevel -local TargetThreatType=Player.Hit[TargetCategory][TargetUnitName].ThreatType -Player.Destroy[TargetCategory]=Player.Destroy[TargetCategory]or{} -Player.Destroy[TargetCategory][TargetType]=Player.Destroy[TargetCategory][TargetType]or{} -local TargetDestroy=Player.Destroy[TargetCategory][TargetType] -TargetDestroy.Score=TargetDestroy.Score or 0 -TargetDestroy.ScoreDestroy=TargetDestroy.ScoreDestroy or 0 -TargetDestroy.Penalty=TargetDestroy.Penalty or 0 -TargetDestroy.PenaltyDestroy=TargetDestroy.PenaltyDestroy or 0 -if TargetCoalition then -if InitCoalition==TargetCoalition then -local ThreatLevelTarget=TargetThreatLevel -local ThreatTypeTarget=TargetThreatType -local ThreatLevelPlayer=Player.ThreatLevel/10+1 -local ThreatPenalty=math.ceil((ThreatLevelTarget/ThreatLevelPlayer)*self.ScaleDestroyPenalty/10) -self:F({ThreatLevel=ThreatPenalty,ThreatLevelTarget=ThreatLevelTarget,ThreatTypeTarget=ThreatTypeTarget,ThreatLevelPlayer=ThreatLevelPlayer}) -Player.Penalty=Player.Penalty+ThreatPenalty -TargetDestroy.Penalty=TargetDestroy.Penalty+ThreatPenalty -TargetDestroy.PenaltyDestroy=TargetDestroy.PenaltyDestroy+1 -self:OnKillPvP(Player,TargetPlayerName,true,TargetThreatLevel,Player.ThreatLevel,ThreatPenalty) -if Player.HitPlayers[TargetPlayerName]then -self:OnKillPvP(Player,TargetPlayerName,true) -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed friendly player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".. -"Penalty: -"..ThreatPenalty.." = "..Player.Score-Player.Penalty, -MESSAGE.Type.Information) -:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition()) -else -self:OnKillPvE(Player,TargetUnitName,true,TargetThreatLevel,Player.ThreatLevel,ThreatPenalty) -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed friendly target "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".. -"Penalty: -"..ThreatPenalty.." = "..Player.Score-Player.Penalty, -MESSAGE.Type.Information) -:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition()) -end -self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_PENALTY",1,ThreatPenalty,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) -Destroyed=true -else -local ThreatLevelTarget=TargetThreatLevel -local ThreatTypeTarget=TargetThreatType -local ThreatLevelPlayer=Player.ThreatLevel/10+1 -local ThreatScore=math.ceil((ThreatLevelTarget/ThreatLevelPlayer)*self.ScaleDestroyScore/10) -self:F({ThreatLevel=ThreatScore,ThreatLevelTarget=ThreatLevelTarget,ThreatTypeTarget=ThreatTypeTarget,ThreatLevelPlayer=ThreatLevelPlayer}) -Player.Score=Player.Score+ThreatScore -TargetDestroy.Score=TargetDestroy.Score+ThreatScore -TargetDestroy.ScoreDestroy=TargetDestroy.ScoreDestroy+1 -if Player.HitPlayers[TargetPlayerName]then -if Player.PlayerKills~=nil then -Player.PlayerKills=Player.PlayerKills+1 -else -Player.PlayerKills=1 -end -self:OnKillPvP(Player,TargetPlayerName,false,TargetThreatLevel,Player.ThreatLevel,ThreatScore) -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed enemy player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".. -"Score: +"..ThreatScore.." = "..Player.Score-Player.Penalty, -MESSAGE.Type.Information) -:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition()) -else -self:OnKillPvE(Player,TargetUnitName,false,TargetThreatLevel,Player.ThreatLevel,ThreatScore) -MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed enemy "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".. -"Score: +"..ThreatScore.." = "..Player.Score-Player.Penalty, -MESSAGE.Type.Information) -:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition()) -end -self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,ThreatScore,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) -Destroyed=true -local UnitName=TargetUnit:GetName() -local Score=self.ScoringObjects[UnitName] -if Score then -Player.Score=Player.Score+Score -TargetDestroy.Score=TargetDestroy.Score+Score -MESSAGE:NewType(self.DisplayMessagePrefix.."Special target '"..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".." destroyed! ".. -"Player '"..PlayerName.."' receives an extra "..Score.." points! Total: "..Player.Score-Player.Penalty, -MESSAGE.Type.Information) -:ToAllIf(self:IfMessagesScore()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesScore()and self:IfMessagesToCoalition()) -self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) -Destroyed=true -end -for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do -self:F({ScoringZone=ScoreZoneData}) -local ScoreZone=ScoreZoneData.ScoreZone -local Score=ScoreZoneData.Score -if ScoreZone:IsVec2InZone(TargetUnit:GetVec2())then -Player.Score=Player.Score+Score -TargetDestroy.Score=TargetDestroy.Score+Score -MESSAGE:NewType(self.DisplayMessagePrefix.."Target destroyed in zone '"..ScoreZone:GetName().."'.".. -"Player '"..PlayerName.."' receives an extra "..Score.." points! ".."Total: "..Player.Score-Player.Penalty, -MESSAGE.Type.Information) -:ToAllIf(self:IfMessagesZone()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesZone()and self:IfMessagesToCoalition()) -self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) -Destroyed=true -end -end -end -else -for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do -self:F({ScoringZone=ScoreZoneData}) -local ScoreZone=ScoreZoneData.ScoreZone -local Score=ScoreZoneData.Score -if ScoreZone:IsVec2InZone(TargetUnit:GetVec2())then -Player.Score=Player.Score+Score -TargetDestroy.Score=TargetDestroy.Score+Score -MESSAGE:NewType(self.DisplayMessagePrefix.."Scenery destroyed in zone '"..ScoreZone:GetName().."'.".. -"Player '"..PlayerName.."' receives an extra "..Score.." points! ".."Total: "..Player.Score-Player.Penalty, -MESSAGE.Type.Information) -:ToAllIf(self:IfMessagesZone()and self:IfMessagesToAll()) -:ToCoalitionIf(InitCoalition,self:IfMessagesZone()and self:IfMessagesToCoalition()) -self:ScoreCSV(PlayerName,"","DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Scenery",TargetUnitType) -Destroyed=true -end -end -end -if Destroyed then -Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0 -end -end -end -end -end -function SCORING:ReportDetailedPlayerHits(PlayerName) -local ScoreMessage="" -local PlayerScore=0 -local PlayerPenalty=0 -local PlayerData=self.Players[PlayerName] -if PlayerData then -self:T("Score Player: "..PlayerName) -local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition] -local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory] -local InitUnitType=PlayerData.UnitType -local InitUnitName=PlayerData.UnitName -local ScoreMessageHits="" -for CategoryID,CategoryName in pairs(_SCORINGCategory)do -self:T(CategoryName) -if PlayerData.Hit[CategoryID]then -self:T("Hit scores exist for player "..PlayerName) -local Score=0 -local ScoreHit=0 -local Penalty=0 -local PenaltyHit=0 -for UnitName,UnitData in pairs(PlayerData.Hit[CategoryID])do -Score=Score+UnitData.Score -ScoreHit=ScoreHit+UnitData.ScoreHit -Penalty=Penalty+UnitData.Penalty -PenaltyHit=UnitData.PenaltyHit -end -local ScoreMessageHit=string.format("%s: %d ",CategoryName,Score-Penalty) -self:T(ScoreMessageHit) -ScoreMessageHits=ScoreMessageHits..ScoreMessageHit -PlayerScore=PlayerScore+Score -PlayerPenalty=PlayerPenalty+Penalty -else -end -end -if ScoreMessageHits~=""then -ScoreMessage="Hits: "..ScoreMessageHits -end -end -return ScoreMessage,PlayerScore,PlayerPenalty -end -function SCORING:ReportDetailedPlayerDestroys(PlayerName) -local ScoreMessage="" -local PlayerScore=0 -local PlayerPenalty=0 -local PlayerData=self.Players[PlayerName] -if PlayerData then -self:T("Score Player: "..PlayerName) -local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition] -local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory] -local InitUnitType=PlayerData.UnitType -local InitUnitName=PlayerData.UnitName -local ScoreMessageDestroys="" -for CategoryID,CategoryName in pairs(_SCORINGCategory)do -if PlayerData.Destroy[CategoryID]then -self:T("Destroy scores exist for player "..PlayerName) -local Score=0 -local ScoreDestroy=0 -local Penalty=0 -local PenaltyDestroy=0 -for UnitName,UnitData in pairs(PlayerData.Destroy[CategoryID])do -self:F({UnitData=UnitData}) -if UnitData~={}then -Score=Score+UnitData.Score -ScoreDestroy=ScoreDestroy+UnitData.ScoreDestroy -Penalty=Penalty+UnitData.Penalty -PenaltyDestroy=PenaltyDestroy+UnitData.PenaltyDestroy -end -end -local ScoreMessageDestroy=string.format(" %s: %d ",CategoryName,Score-Penalty) -self:T(ScoreMessageDestroy) -ScoreMessageDestroys=ScoreMessageDestroys..ScoreMessageDestroy -PlayerScore=PlayerScore+Score -PlayerPenalty=PlayerPenalty+Penalty -else -end -end -if ScoreMessageDestroys~=""then -ScoreMessage="Destroys: "..ScoreMessageDestroys -end -end -return ScoreMessage,PlayerScore,PlayerPenalty -end -function SCORING:ReportDetailedPlayerCoalitionChanges(PlayerName) -local ScoreMessage="" -local PlayerScore=0 -local PlayerPenalty=0 -local PlayerData=self.Players[PlayerName] -if PlayerData then -self:T("Score Player: "..PlayerName) -local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition] -local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory] -local InitUnitType=PlayerData.UnitType -local InitUnitName=PlayerData.UnitName -local ScoreMessageCoalitionChangePenalties="" -if PlayerData.PenaltyCoalition~=0 then -ScoreMessageCoalitionChangePenalties=ScoreMessageCoalitionChangePenalties..string.format(" -%d (%d changed)",PlayerData.Penalty,PlayerData.PenaltyCoalition) -PlayerPenalty=PlayerPenalty+PlayerData.Penalty -end -if ScoreMessageCoalitionChangePenalties~=""then -ScoreMessage=ScoreMessage.."Coalition Penalties: "..ScoreMessageCoalitionChangePenalties -end -end -return ScoreMessage,PlayerScore,PlayerPenalty -end -function SCORING:ReportDetailedPlayerGoals(PlayerName) -local ScoreMessage="" -local PlayerScore=0 -local PlayerPenalty=0 -local PlayerData=self.Players[PlayerName] -if PlayerData then -self:T("Score Player: "..PlayerName) -local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition] -local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory] -local InitUnitType=PlayerData.UnitType -local InitUnitName=PlayerData.UnitName -local ScoreMessageGoal="" -local ScoreGoal=0 -local ScoreTask=0 -for GoalName,GoalData in pairs(PlayerData.Goals)do -ScoreGoal=ScoreGoal+GoalData.Score -ScoreMessageGoal=ScoreMessageGoal.."'"..GoalName.."':"..GoalData.Score.."; " -end -PlayerScore=PlayerScore+ScoreGoal -if ScoreMessageGoal~=""then -ScoreMessage="Goals: "..ScoreMessageGoal -end -end -return ScoreMessage,PlayerScore,PlayerPenalty -end -function SCORING:ReportDetailedPlayerMissions(PlayerName) -local ScoreMessage="" -local PlayerScore=0 -local PlayerPenalty=0 -local PlayerData=self.Players[PlayerName] -if PlayerData then -self:T("Score Player: "..PlayerName) -local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition] -local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory] -local InitUnitType=PlayerData.UnitType -local InitUnitName=PlayerData.UnitName -local ScoreMessageMission="" -local ScoreMission=0 -local ScoreTask=0 -for MissionName,MissionData in pairs(PlayerData.Mission)do -ScoreMission=ScoreMission+MissionData.ScoreMission -ScoreTask=ScoreTask+MissionData.ScoreTask -ScoreMessageMission=ScoreMessageMission.."'"..MissionName.."'; " -end -PlayerScore=PlayerScore+ScoreMission+ScoreTask -if ScoreMessageMission~=""then -ScoreMessage="Tasks: "..ScoreTask.." Mission: "..ScoreMission.." ( "..ScoreMessageMission..")" -end -end -return ScoreMessage,PlayerScore,PlayerPenalty -end -function SCORING:ReportScoreGroupSummary(PlayerGroup) -local PlayerMessage="" -self:T("Report Score Group Summary") -local PlayerUnits=PlayerGroup:GetUnits() -for UnitID,PlayerUnit in pairs(PlayerUnits)do -local PlayerUnit=PlayerUnit -local PlayerName=PlayerUnit:GetPlayerName() -if PlayerName then -local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName) -ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits -self:F({ReportHits,ScoreHits,PenaltyHits}) -local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName) -ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys -self:F({ReportDestroys,ScoreDestroys,PenaltyDestroys}) -local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName) -ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges -self:F({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges}) -local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName) -ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals -self:F({ReportGoals,ScoreGoals,PenaltyGoals}) -local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName) -ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions -self:F({ReportMissions,ScoreMissions,PenaltyMissions}) -local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions -local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+PenaltyGoals+PenaltyMissions -PlayerMessage=string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )", -PlayerName, -PlayerScore-PlayerPenalty, -PlayerScore, -PlayerPenalty -) -MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup) -end -end -end -function SCORING:ReportScoreGroupDetailed(PlayerGroup) -local PlayerMessage="" -self:T("Report Score Group Detailed") -local PlayerUnits=PlayerGroup:GetUnits() -for UnitID,PlayerUnit in pairs(PlayerUnits)do -local PlayerUnit=PlayerUnit -local PlayerName=PlayerUnit:GetPlayerName() -if PlayerName then -local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName) -ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits -self:F({ReportHits,ScoreHits,PenaltyHits}) -local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName) -ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys -self:F({ReportDestroys,ScoreDestroys,PenaltyDestroys}) -local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName) -ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges -self:F({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges}) -local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName) -ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals -self:F({ReportGoals,ScoreGoals,PenaltyGoals}) -local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName) -ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions -self:F({ReportMissions,ScoreMissions,PenaltyMissions}) -local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions -local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+PenaltyGoals+PenaltyMissions -PlayerMessage= -string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s", -PlayerName, -PlayerScore-PlayerPenalty, -PlayerScore, -PlayerPenalty, -ReportHits, -ReportDestroys, -ReportCoalitionChanges, -ReportGoals, -ReportMissions -) -MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup) -end -end -end -function SCORING:ReportScoreAllSummary(PlayerGroup) -local PlayerMessage="" -self:T({"Summary Score Report of All Players",Players=self.Players}) -for PlayerName,PlayerData in pairs(self.Players)do -self:T({PlayerName=PlayerName,PlayerGroup=PlayerGroup}) -if PlayerName then -local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName) -ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits -self:F({ReportHits,ScoreHits,PenaltyHits}) -local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName) -ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys -self:F({ReportDestroys,ScoreDestroys,PenaltyDestroys}) -local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName) -ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges -self:F({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges}) -local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName) -ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals -self:F({ReportGoals,ScoreGoals,PenaltyGoals}) -local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName) -ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions -self:F({ReportMissions,ScoreMissions,PenaltyMissions}) -local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions -local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+PenaltyGoals+PenaltyMissions -PlayerMessage= -string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )", -PlayerName, -PlayerScore-PlayerPenalty, -PlayerScore, -PlayerPenalty -) -MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup) -end -end -end -function SCORING:SecondsToClock(sSeconds) -local nSeconds=sSeconds -if nSeconds==0 then -return"00:00:00"; -else -local nHours=string.format("%02.f",math.floor(nSeconds/3600)); -local nMins=string.format("%02.f",math.floor(nSeconds/60-(nHours*60))); -local nSecs=string.format("%02.f",math.floor(nSeconds-nHours*3600-nMins*60)); -return nHours..":"..nMins..":"..nSecs -end -end -function SCORING:OpenCSV(ScoringCSV) -self:F(ScoringCSV) -if lfs and io and os and self.AutoSave then -if ScoringCSV then -self.ScoringCSV=ScoringCSV -local fdir=lfs.writedir()..[[Logs\]]..self.ScoringCSV.." "..os.date("%Y-%m-%d %H-%M-%S")..".csv" -self.CSVFile,self.err=io.open(fdir,"w+") -if not self.CSVFile then -error("Error: Cannot open CSV file in "..lfs.writedir()) -end -self.CSVFile:write('"GameName","RunTime","Time","PlayerName","TargetPlayerName","ScoreType","PlayerUnitCoalition","PlayerUnitCategory","PlayerUnitType","PlayerUnitName","TargetUnitCoalition","TargetUnitCategory","TargetUnitType","TargetUnitName","Times","Score"\n') -self.RunTime=os.date("%y-%m-%d_%H-%M-%S") -else -error("A string containing the CSV file name must be given.") -end -else -self:F("The MissionScripting.lua file has not been changed to allow lfs, io and os modules to be used...") -end -return self -end -function SCORING:ScoreCSV(PlayerName,TargetPlayerName,ScoreType,ScoreTimes,ScoreAmount,PlayerUnitName,PlayerUnitCoalition,PlayerUnitCategory,PlayerUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) -local ScoreTime=self:SecondsToClock(timer.getTime()) -PlayerName=PlayerName:gsub('"','_') -TargetPlayerName=TargetPlayerName or"" -TargetPlayerName=TargetPlayerName:gsub('"','_') -if PlayerUnitName and PlayerUnitName~=''then -local PlayerUnit=Unit.getByName(PlayerUnitName) -if PlayerUnit then -if not PlayerUnitCategory then -PlayerUnitCategory=_SCORINGCategory[PlayerUnit:getDesc().category] -end -if not PlayerUnitCoalition then -PlayerUnitCoalition=_SCORINGCoalition[PlayerUnit:getCoalition()] -end -if not PlayerUnitType then -PlayerUnitType=PlayerUnit:getTypeName() -end -else -PlayerUnitName='' -PlayerUnitCategory='' -PlayerUnitCoalition='' -PlayerUnitType='' -end -else -PlayerUnitName='' -PlayerUnitCategory='' -PlayerUnitCoalition='' -PlayerUnitType='' -end -TargetUnitCoalition=TargetUnitCoalition or"" -TargetUnitCategory=TargetUnitCategory or"" -TargetUnitType=TargetUnitType or"" -TargetUnitName=TargetUnitName or"" -if lfs and io and os and self.AutoSave then -self.CSVFile:write( -'"'..self.GameName..'"'..','.. -'"'..self.RunTime..'"'..','.. -''..ScoreTime..''..','.. -'"'..PlayerName..'"'..','.. -'"'..TargetPlayerName..'"'..','.. -'"'..ScoreType..'"'..','.. -'"'..PlayerUnitCoalition..'"'..','.. -'"'..PlayerUnitCategory..'"'..','.. -'"'..PlayerUnitType..'"'..','.. -'"'..PlayerUnitName..'"'..','.. -'"'..TargetUnitCoalition..'"'..','.. -'"'..TargetUnitCategory..'"'..','.. -'"'..TargetUnitType..'"'..','.. -'"'..TargetUnitName..'"'..','.. -''..ScoreTimes..''..','.. -''..ScoreAmount -) -self.CSVFile:write("\n") -end -end -function SCORING:CloseCSV() -if lfs and io and os and self.AutoSave then -self.CSVFile:close() -end -end -function SCORING:SwitchAutoSave(OnOff) -self.AutoSave=OnOff -return self -end -function SCORING:OnKillPvP(Player,TargetPlayerName,IsTeamKill,TargetThreatLevel,PlayerThreatLevel,Score) -end -function SCORING:OnKillPvE(Player,TargetUnitName,IsTeamKill,TargetThreatLevel,PlayerThreatLevel,Score) -end -CLEANUP_AIRBASE={ -ClassName="CLEANUP_AIRBASE", -TimeInterval=0.2, -CleanUpList={}, -} -CLEANUP_AIRBASE.__={} -CLEANUP_AIRBASE.__.Airbases={} -function CLEANUP_AIRBASE:New(AirbaseNames) -local self=BASE:Inherit(self,BASE:New()) -self:F({AirbaseNames}) -if type(AirbaseNames)=='table'then -for AirbaseID,AirbaseName in pairs(AirbaseNames)do -self:AddAirbase(AirbaseName) -end -else -local AirbaseName=AirbaseNames -self:AddAirbase(AirbaseName) -end -self:HandleEvent(EVENTS.Birth,self.__.OnEventBirth) -self.__.CleanUpScheduler=SCHEDULER:New(self,self.__.CleanUpSchedule,{},1,self.TimeInterval) -self:HandleEvent(EVENTS.EngineShutdown,self.__.EventAddForCleanUp) -self:HandleEvent(EVENTS.EngineStartup,self.__.EventAddForCleanUp) -self:HandleEvent(EVENTS.Hit,self.__.EventAddForCleanUp) -self:HandleEvent(EVENTS.PilotDead,self.__.OnEventCrash) -self:HandleEvent(EVENTS.Dead,self.__.OnEventCrash) -self:HandleEvent(EVENTS.Crash,self.__.OnEventCrash) -for UnitName,Unit in pairs(_DATABASE.UNITS)do -local Unit=Unit -if Unit:IsAlive()~=nil then -if self:IsInAirbase(Unit:GetVec2())then -self:F({UnitName=UnitName}) -self.CleanUpList[UnitName]={} -self.CleanUpList[UnitName].CleanUpUnit=Unit -self.CleanUpList[UnitName].CleanUpGroup=Unit:GetGroup() -self.CleanUpList[UnitName].CleanUpGroupName=Unit:GetGroup():GetName() -self.CleanUpList[UnitName].CleanUpUnitName=Unit:GetName() -end -end -end -return self -end -function CLEANUP_AIRBASE:AddAirbase(AirbaseName) -self.__.Airbases[AirbaseName]=AIRBASE:FindByName(AirbaseName) -self:F({"Airbase:",AirbaseName,self.__.Airbases[AirbaseName]:GetDesc()}) -return self -end -function CLEANUP_AIRBASE:RemoveAirbase(AirbaseName) -self.__.Airbases[AirbaseName]=nil -return self -end -function CLEANUP_AIRBASE:SetCleanMissiles(CleanMissiles) -if CleanMissiles then -self:HandleEvent(EVENTS.Shot,self.__.OnEventShot) -else -self:UnHandleEvent(EVENTS.Shot) -end -end -function CLEANUP_AIRBASE.__:IsInAirbase(Vec2) -local InAirbase=false -for AirbaseName,Airbase in pairs(self.__.Airbases)do -local Airbase=Airbase -if Airbase:GetZone():IsVec2InZone(Vec2)then -InAirbase=true -break; -end -end -return InAirbase -end -function CLEANUP_AIRBASE.__:DestroyUnit(CleanUpUnit) -self:F({CleanUpUnit}) -if CleanUpUnit then -local CleanUpUnitName=CleanUpUnit:GetName() -local CleanUpGroup=CleanUpUnit:GetGroup() -if CleanUpGroup:IsAlive()then -local CleanUpGroupUnits=CleanUpGroup:GetUnits() -if#CleanUpGroupUnits==1 then -local CleanUpGroupName=CleanUpGroup:GetName() -CleanUpGroup:Destroy() -else -CleanUpUnit:Destroy() -end -self.CleanUpList[CleanUpUnitName]=nil -end -end -end -function CLEANUP_AIRBASE.__:DestroyMissile(MissileObject) -self:F({MissileObject}) -if MissileObject and MissileObject:isExist()then -MissileObject:destroy() -self:T("MissileObject Destroyed") -end -end -function CLEANUP_AIRBASE.__:OnEventBirth(EventData) -self:F({EventData}) -if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive()~=nil then -if self:IsInAirbase(EventData.IniUnit:GetVec2())then -self.CleanUpList[EventData.IniDCSUnitName]={} -self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnit=EventData.IniUnit -self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroup=EventData.IniGroup -self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroupName=EventData.IniDCSGroupName -self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnitName=EventData.IniDCSUnitName -end -end -end -function CLEANUP_AIRBASE.__:OnEventCrash(Event) -self:F({Event}) -if Event.IniDCSUnitName and Event.IniCategory==Object.Category.UNIT then -self.CleanUpList[Event.IniDCSUnitName]={} -self.CleanUpList[Event.IniDCSUnitName].CleanUpUnit=Event.IniUnit -self.CleanUpList[Event.IniDCSUnitName].CleanUpGroup=Event.IniGroup -self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName=Event.IniDCSGroupName -self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName=Event.IniDCSUnitName -end -end -function CLEANUP_AIRBASE.__:OnEventShot(Event) -self:F({Event}) -if self:IsInAirbase(Event.IniUnit:GetVec2())then -self:DestroyMissile(Event.Weapon) -end -end -function CLEANUP_AIRBASE.__:OnEventHit(Event) -self:F({Event}) -if Event.IniUnit then -if self:IsInAirbase(Event.IniUnit:GetVec2())then -self:T({"Life: ",Event.IniDCSUnitName,' = ',Event.IniUnit:GetLife(),"/",Event.IniUnit:GetLife0()}) -if Event.IniUnit:GetLife()0 then -local MoveProbability=(self.MoveMaximum*100)/self.AliveUnits -self:T('Move Probability = '..MoveProbability) -for MovementUnitName,MovementGroupName in pairs(self.MoveUnits)do -local MovementGroup=Group.getByName(MovementGroupName) -if MovementGroup and MovementGroup:isExist()then -local MoveOrStop=math.random(1,100) -self:T('MoveOrStop = '..MoveOrStop) -if MoveOrStop<=MoveProbability then -self:T('Group continues moving = '..MovementGroupName) -trigger.action.groupContinueMoving(MovementGroup) -else -self:T('Group stops moving = '..MovementGroupName) -trigger.action.groupStopMoving(MovementGroup) -end -else -self.MoveUnits[MovementUnitName]=nil -end -end -end -return true -end -SEAD={ -ClassName="SEAD", -TargetSkill={ -Average={Evade=30,DelayOn={40,60}}, -Good={Evade=20,DelayOn={30,50}}, -High={Evade=15,DelayOn={20,40}}, -Excellent={Evade=10,DelayOn={10,30}} -}, -SEADGroupPrefixes={}, -SuppressedGroups={}, -EngagementRange=75, -Padding=10, -CallBack=nil, -UseCallBack=false, -debug=false, -} -SEAD.Harms={ -["AGM_88"]="AGM_88", -["AGM_122"]="AGM_122", -["AGM_84"]="AGM_84", -["AGM_45"]="AGM_45", -["ALARM"]="ALARM", -["LD-10"]="LD-10", -["X_58"]="X_58", -["X_28"]="X_28", -["X_25"]="X_25", -["X_31"]="X_31", -["Kh25"]="Kh25", -["BGM_109"]="BGM_109", -["AGM_154"]="AGM_154", -["HY-2"]="HY-2", -["ADM_141A"]="ADM_141A", -} -SEAD.HarmData={ -["AGM_88"]={150,3}, -["AGM_45"]={12,2}, -["AGM_122"]={16.5,2.3}, -["AGM_84"]={280,0.8}, -["ALARM"]={45,2}, -["LD-10"]={60,4}, -["X_58"]={70,4}, -["X_28"]={80,2.5}, -["X_25"]={25,0.76}, -["X_31"]={150,3}, -["Kh25"]={25,0.8}, -["BGM_109"]={460,0.705}, -["AGM_154"]={130,0.61}, -["HY-2"]={90,1}, -["ADM_141A"]={126,0.6}, -} -function SEAD:New(SEADGroupPrefixes,Padding) -local self=BASE:Inherit(self,FSM:New()) -self:T(SEADGroupPrefixes) -if type(SEADGroupPrefixes)=='table'then -for SEADGroupPrefixID,SEADGroupPrefix in pairs(SEADGroupPrefixes)do -self.SEADGroupPrefixes[SEADGroupPrefix]=SEADGroupPrefix -end -else -self.SEADGroupPrefixes[SEADGroupPrefixes]=SEADGroupPrefixes -end -local padding=Padding or 10 -if padding<10 then padding=10 end -self.Padding=padding -self.UseEmissionsOnOff=true -self.debug=false -self.CallBack=nil -self.UseCallBack=false -self:HandleEvent(EVENTS.Shot,self.HandleEventShot) -self:SetStartState("Running") -self:AddTransition("*","ManageEvasion","*") -self:AddTransition("*","CalculateHitZone","*") -self:I("*** SEAD - Started Version 0.4.6") -return self -end -function SEAD:UpdateSet(SEADGroupPrefixes) -self:T(SEADGroupPrefixes) -if type(SEADGroupPrefixes)=='table'then -for SEADGroupPrefixID,SEADGroupPrefix in pairs(SEADGroupPrefixes)do -self.SEADGroupPrefixes[SEADGroupPrefix]=SEADGroupPrefix -end -else -self.SEADGroupPrefixes[SEADGroupPrefixes]=SEADGroupPrefixes -end -return self -end -function SEAD:SetEngagementRange(range) -self:T({range}) -range=range or 75 -if range<0 or range>100 then -range=75 -end -self.EngagementRange=range -self:T(string.format("*** SEAD - Engagement range set to %s",range)) -return self -end -function SEAD:SetPadding(Padding) -self:T({Padding}) -local padding=Padding or 10 -if padding<10 then padding=10 end -self.Padding=padding -return self -end -function SEAD:SwitchEmissions(Switch) -self:T({Switch}) -self.UseEmissionsOnOff=Switch -return self -end -function SEAD:AddCallBack(Object) -self:T({Class=Object.ClassName}) -self.CallBack=Object -self.UseCallBack=true -return self -end -function SEAD:_CheckHarms(WeaponName) -self:T({WeaponName}) -local hit=false -local name="" -for _,_name in pairs(SEAD.Harms)do -if string.find(WeaponName,_name,1,true)then -hit=true -name=_name -break -end -end -return hit,name -end -function SEAD:_GetDistance(_point1,_point2) -self:T("_GetDistance") -if _point1 and _point2 then -local distance1=_point1:Get2DDistance(_point2) -local distance2=_point1:DistanceFromPointVec2(_point2) -if distance1 and type(distance1)=="number"then -return distance1 -elseif distance2 and type(distance2)=="number"then -return distance2 -else -self:E("*****Cannot calculate distance!") -self:E({_point1,_point2}) -return-1 -end -else -self:E("******Cannot calculate distance!") -self:E({_point1,_point2}) -return-1 -end -end -function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup,SEADWeaponName) -self:T("**** Calculating hit zone for "..(SEADWeaponName or"None")) -if SEADWeapon and SEADWeapon:isExist()then -local position=SEADWeapon:getPosition() -local mheight=height -local wph=math.atan2(position.x.z,position.x.x) -if wph<0 then -wph=wph+2*math.pi -end -wph=math.deg(wph) -local wpndata=SEAD.HarmData["AGM_88"] -if string.find(SEADWeaponName,"154",1)then -wpndata=SEAD.HarmData["AGM_154"] -end -local mveloc=math.floor(wpndata[2]*340.29) -local c1=(2*mheight*9.81)/(mveloc^2) -local c2=(mveloc^2)/9.81 -local Ropt=c2*math.sqrt(c1+1) -if height<=5000 then -Ropt=Ropt*0.72 -elseif height<=7500 then -Ropt=Ropt*0.82 -elseif height<=10000 then -Ropt=Ropt*0.87 -elseif height<=12500 then -Ropt=Ropt*0.98 -end -for n=1,3 do -local dist=Ropt-((n-1)*20000) -local predpos=pos0:Translate(dist,wph) -if predpos then -local targetzone=ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000) -if self.debug then -predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false) -targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true) -end -local seadset=SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce() -local tgtcoord=targetzone:GetRandomPointVec2() -local tgtgrp=seadset:GetRandom() -local _targetgroup=nil -local _targetgroupname="none" -local _targetskill="Random" -if tgtgrp and tgtgrp:IsAlive()then -_targetgroup=tgtgrp -_targetgroupname=tgtgrp:GetName() -_targetskill=tgtgrp:GetUnit(1):GetSkill() -self:T("*** Found Target = ".._targetgroupname) -self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup,20) -end -end -end -end -return self -end -function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,timeoffset,Weapon) -local timeoffset=timeoffset or 0 -if _targetskill=="Random"then -local Skills={"Average","Good","High","Excellent"} -_targetskill=Skills[math.random(1,4)] -end -if self.TargetSkill[_targetskill]then -local _evade=math.random(1,100) -if(_evade>self.TargetSkill[_targetskill].Evade)then -self:T("*** SEAD - Evading") -local _targetpos=_targetgroup:GetCoordinate() -local _distance=self:_GetDistance(SEADPlanePos,_targetpos) -local hit,data=self:_CheckHarms(SEADWeaponName) -local wpnspeed=666 -local reach=10 -if hit then -local wpndata=SEAD.HarmData[data] -reach=wpndata[1]*1.1 -local mach=wpndata[2] -wpnspeed=math.floor(mach*340.29) -if Weapon then -wpnspeed=Weapon:GetSpeed() -self:T(string.format("*** SEAD - Weapon Speed from WEAPON: %f m/s",wpnspeed)) -end -end -local _tti=math.floor(_distance/wpnspeed)-timeoffset -if _distance>0 then -_distance=math.floor(_distance/1000) -else -_distance=0 -end -self:T(string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec",_targetskill,_distance,reach,_tti)) -if reach>=_distance then -self:T("*** SEAD - Shot in Reach") -local function SuppressionStart(args) -self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2])) -local grp=args[1] -local name=args[2] -local attacker=args[3] -if self.UseEmissionsOnOff then -grp:EnableEmission(false) -end -grp:OptionAlarmStateGreen() -grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond",true) -if self.UseCallBack then -local object=self.CallBack -object:SeadSuppressionStart(grp,name,attacker) -end -end -local function SuppressionStop(args) -self:T(string.format("*** SEAD - %s Radar On",args[2])) -local grp=args[1] -local name=args[2] -if self.UseEmissionsOnOff then -grp:EnableEmission(true) -end -grp:OptionAlarmStateRed() -grp:OptionEngageRange(self.EngagementRange) -self.SuppressedGroups[name]=false -if self.UseCallBack then -local object=self.CallBack -object:SeadSuppressionEnd(grp,name) -end -end -local delay=math.random(self.TargetSkill[_targetskill].DelayOn[1],self.TargetSkill[_targetskill].DelayOn[2]) -if delay>_tti then delay=delay/2 end -if _tti>600 then delay=_tti-90 end -local SuppressionStartTime=timer.getTime()+delay -local SuppressionEndTime=timer.getTime()+delay+_tti+self.Padding+delay -local _targetgroupname=_targetgroup:GetName() -if not self.SuppressedGroups[_targetgroupname]then -self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay)) -timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname,SEADGroup},SuppressionStartTime) -timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime) -self.SuppressedGroups[_targetgroupname]=true -if self.UseCallBack then -local object=self.CallBack -object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime,SEADGroup) -end -end -end -end -end -return self -end -function SEAD:HandleEventShot(EventData) -self:T({EventData.id}) -local SEADPlane=EventData.IniUnit -local SEADGroup=EventData.IniGroup -local SEADPlanePos=SEADPlane:GetCoordinate() -local SEADUnit=EventData.IniDCSUnit -local SEADUnitName=EventData.IniDCSUnitName -local SEADWeapon=EventData.Weapon -local SEADWeaponName=EventData.WeaponName -local WeaponWrapper=WEAPON:New(EventData.Weapon) -self:T("*** SEAD - Missile Launched = "..SEADWeaponName) -if self:_CheckHarms(SEADWeaponName)then -self:T('*** SEAD - Weapon Match') -local _targetskill="Random" -local _targetgroupname="none" -local _target=EventData.Weapon:getTarget() -if not _target or self.debug then -self:E("***** SEAD - No target data for "..(SEADWeaponName or"None")) -if string.find(SEADWeaponName,"AGM_88",1,true)or string.find(SEADWeaponName,"AGM_154",1,true)then -self:I("**** Tracking AGM-88/154 with no target data.") -local pos0=SEADPlane:GetCoordinate() -local fheight=SEADPlane:GetHeight() -self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName) -end -return self -end -local targetcat=Object.getCategory(_target) -local _targetUnit=nil -local _targetgroup=nil -self:T(string.format("*** Targetcat = %d",targetcat)) -if targetcat==Object.Category.UNIT then -self:T("*** Target Category UNIT") -_targetUnit=UNIT:Find(_target) -if _targetUnit and _targetUnit:IsAlive()then -_targetgroup=_targetUnit:GetGroup() -_targetgroupname=_targetgroup:GetName() -local _targetUnitName=_targetUnit:GetName() -_targetUnit:GetSkill() -_targetskill=_targetUnit:GetSkill() -end -elseif targetcat==Object.Category.STATIC then -self:T("*** Target Category STATIC") -local seadset=SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterOnce() -local targetpoint=_target:getPoint()or{x=0,y=0,z=0} -local tgtcoord=COORDINATE:NewFromVec3(targetpoint) -local tgtgrp=seadset:FindNearestGroupFromPointVec2(tgtcoord) -if tgtgrp and tgtgrp:IsAlive()then -_targetgroup=tgtgrp -_targetgroupname=tgtgrp:GetName() -_targetskill=tgtgrp:GetUnit(1):GetSkill() -self:T("*** Found Target = ".._targetgroupname) -end -end -local SEADGroupFound=false -for SEADGroupPrefixID,SEADGroupPrefix in pairs(self.SEADGroupPrefixes)do -self:T("Target = ".._targetgroupname.." | Prefix = "..SEADGroupPrefix) -if string.find(_targetgroupname,SEADGroupPrefix,1,true)then -SEADGroupFound=true -self:T('*** SEAD - Group Match Found') -break -end -end -if SEADGroupFound==true then -if string.find(SEADWeaponName,"ADM_141",1,true)then -self:__ManageEvasion(2,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper) -else -self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper) -end -end -end -return self -end -ESCORT={ -ClassName="ESCORT", -EscortName=nil, -EscortClient=nil, -EscortGroup=nil, -EscortMode=1, -MODE={ -FOLLOW=1, -MISSION=2, -}, -Targets={}, -FollowScheduler=nil, -ReportTargets=true, -OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE, -OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, -SmokeDirectionVector=false, -TaskPoints={} -} -function ESCORT:New(EscortClient,EscortGroup,EscortName,EscortBriefing) -local self=BASE:Inherit(self,BASE:New()) -self:F({EscortClient,EscortGroup,EscortName}) -self.EscortClient=EscortClient -self.EscortGroup=EscortGroup -self.EscortName=EscortName -self.EscortBriefing=EscortBriefing -self.EscortSetGroup=SET_GROUP:New() -self.EscortSetGroup:AddObject(self.EscortGroup) -self.EscortSetGroup:Flush() -self.Detection=DETECTION_UNITS:New(self.EscortSetGroup,15000) -self.EscortGroup.Detection=self.Detection -if not self.EscortClient._EscortGroups then -self.EscortClient._EscortGroups={} -end -if not self.EscortClient._EscortGroups[EscortGroup:GetName()]then -self.EscortClient._EscortGroups[EscortGroup:GetName()]={} -self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortGroup=self.EscortGroup -self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortName=self.EscortName -self.EscortClient._EscortGroups[EscortGroup:GetName()].Detection=self.EscortGroup.Detection -end -self.EscortMenu=MENU_GROUP:New(self.EscortClient:GetGroup(),self.EscortName) -self.EscortGroup:WayPointInitialize(1) -self.EscortGroup:OptionROTVertical() -self.EscortGroup:OptionROEOpenFire() -if not EscortBriefing then -EscortGroup:MessageToClient(EscortGroup:GetCategoryName().." '"..EscortName.."' ("..EscortGroup:GetCallsign()..") reporting! ".. -"We're escorting your flight. ".. -"Use the Radio Menu and F10 and use the options under + "..EscortName.."\n", -60,EscortClient -) -else -EscortGroup:MessageToClient(EscortGroup:GetCategoryName().." '"..EscortName.."' ("..EscortGroup:GetCallsign()..") "..EscortBriefing, -60,EscortClient -) -end -self.FollowDistance=100 -self.CT1=0 -self.GT1=0 -self.FollowScheduler,self.FollowSchedule=SCHEDULER:New(self,self._FollowScheduler,{},1,.5,.01) -self.FollowScheduler:Stop(self.FollowSchedule) -self.EscortMode=ESCORT.MODE.MISSION -return self -end -function ESCORT:SetDetection(Detection) -self.Detection=Detection -self.EscortGroup.Detection=self.Detection -self.EscortClient._EscortGroups[self.EscortGroup:GetName()].Detection=self.EscortGroup.Detection -Detection:__Start(1) -end -function ESCORT:TestSmokeDirectionVector(SmokeDirection) -self.SmokeDirectionVector=(SmokeDirection==true)and true or false -end -function ESCORT:Menus() -self:F() -self:MenuFollowAt(100) -self:MenuFollowAt(200) -self:MenuFollowAt(300) -self:MenuFollowAt(400) -self:MenuScanForTargets(100,60) -self:MenuHoldAtEscortPosition(30) -self:MenuHoldAtLeaderPosition(30) -self:MenuFlare() -self:MenuSmoke() -self:MenuReportTargets(60) -self:MenuAssistedAttack() -self:MenuROE() -self:MenuEvasion() -self:MenuResumeMission() -return self -end -function ESCORT:MenuFollowAt(Distance) -self:F(Distance) -if self.EscortGroup:IsAir()then -if not self.EscortMenuReportNavigation then -self.EscortMenuReportNavigation=MENU_GROUP:New(self.EscortClient:GetGroup(),"Navigation",self.EscortMenu) -end -if not self.EscortMenuJoinUpAndFollow then -self.EscortMenuJoinUpAndFollow={} -end -self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1]=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Join-Up and Follow at "..Distance,self.EscortMenuReportNavigation,ESCORT._JoinUpAndFollow,self,Distance) -self.EscortMode=ESCORT.MODE.FOLLOW -end -return self -end -function ESCORT:MenuHoldAtEscortPosition(Height,Seconds,MenuTextFormat) -self:F({Height,Seconds,MenuTextFormat}) -if self.EscortGroup:IsAir()then -if not self.EscortMenuHold then -self.EscortMenuHold=MENU_GROUP:New(self.EscortClient:GetGroup(),"Hold position",self.EscortMenu) -end -if not Height then -Height=30 -end -if not Seconds then -Seconds=0 -end -local MenuText="" -if not MenuTextFormat then -if Seconds==0 then -MenuText=string.format("Hold at %d meter",Height) -else -MenuText=string.format("Hold at %d meter for %d seconds",Height,Seconds) -end -else -if Seconds==0 then -MenuText=string.format(MenuTextFormat,Height) -else -MenuText=string.format(MenuTextFormat,Height,Seconds) -end -end -if not self.EscortMenuHoldPosition then -self.EscortMenuHoldPosition={} -end -self.EscortMenuHoldPosition[#self.EscortMenuHoldPosition+1]=MENU_GROUP_COMMAND -:New( -self.EscortClient:GetGroup(), -MenuText, -self.EscortMenuHold, -ESCORT._HoldPosition, -self, -self.EscortGroup, -Height, -Seconds -) -end -return self -end -function ESCORT:MenuHoldAtLeaderPosition(Height,Seconds,MenuTextFormat) -self:F({Height,Seconds,MenuTextFormat}) -if self.EscortGroup:IsAir()then -if not self.EscortMenuHold then -self.EscortMenuHold=MENU_GROUP:New(self.EscortClient:GetGroup(),"Hold position",self.EscortMenu) -end -if not Height then -Height=30 -end -if not Seconds then -Seconds=0 -end -local MenuText="" -if not MenuTextFormat then -if Seconds==0 then -MenuText=string.format("Rejoin and hold at %d meter",Height) -else -MenuText=string.format("Rejoin and hold at %d meter for %d seconds",Height,Seconds) -end -else -if Seconds==0 then -MenuText=string.format(MenuTextFormat,Height) -else -MenuText=string.format(MenuTextFormat,Height,Seconds) -end -end -if not self.EscortMenuHoldAtLeaderPosition then -self.EscortMenuHoldAtLeaderPosition={} -end -self.EscortMenuHoldAtLeaderPosition[#self.EscortMenuHoldAtLeaderPosition+1]=MENU_GROUP_COMMAND -:New( -self.EscortClient:GetGroup(), -MenuText, -self.EscortMenuHold, -ESCORT._HoldPosition, -{ParamSelf=self, -ParamOrbitGroup=self.EscortClient, -ParamHeight=Height, -ParamSeconds=Seconds -} -) -end -return self -end -function ESCORT:MenuScanForTargets(Height,Seconds,MenuTextFormat) -self:F({Height,Seconds,MenuTextFormat}) -if self.EscortGroup:IsAir()then -if not self.EscortMenuScan then -self.EscortMenuScan=MENU_GROUP:New(self.EscortClient:GetGroup(),"Scan for targets",self.EscortMenu) -end -if not Height then -Height=100 -end -if not Seconds then -Seconds=30 -end -local MenuText="" -if not MenuTextFormat then -if Seconds==0 then -MenuText=string.format("At %d meter",Height) -else -MenuText=string.format("At %d meter for %d seconds",Height,Seconds) -end -else -if Seconds==0 then -MenuText=string.format(MenuTextFormat,Height) -else -MenuText=string.format(MenuTextFormat,Height,Seconds) -end -end -if not self.EscortMenuScanForTargets then -self.EscortMenuScanForTargets={} -end -self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1]=MENU_GROUP_COMMAND -:New( -self.EscortClient:GetGroup(), -MenuText, -self.EscortMenuScan, -ESCORT._ScanTargets, -self, -30 -) -end -return self -end -function ESCORT:MenuFlare(MenuTextFormat) -self:F() -if not self.EscortMenuReportNavigation then -self.EscortMenuReportNavigation=MENU_GROUP:New(self.EscortClient:GetGroup(),"Navigation",self.EscortMenu) -end -local MenuText="" -if not MenuTextFormat then -MenuText="Flare" -else -MenuText=MenuTextFormat -end -if not self.EscortMenuFlare then -self.EscortMenuFlare=MENU_GROUP:New(self.EscortClient:GetGroup(),MenuText,self.EscortMenuReportNavigation,ESCORT._Flare,self) -self.EscortMenuFlareGreen=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release green flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Green,"Released a green flare!") -self.EscortMenuFlareRed=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release red flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Red,"Released a red flare!") -self.EscortMenuFlareWhite=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release white flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.White,"Released a white flare!") -self.EscortMenuFlareYellow=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release yellow flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Yellow,"Released a yellow flare!") -end -return self -end -function ESCORT:MenuSmoke(MenuTextFormat) -self:F() -if not self.EscortGroup:IsAir()then -if not self.EscortMenuReportNavigation then -self.EscortMenuReportNavigation=MENU_GROUP:New(self.EscortClient:GetGroup(),"Navigation",self.EscortMenu) -end -local MenuText="" -if not MenuTextFormat then -MenuText="Smoke" -else -MenuText=MenuTextFormat -end -if not self.EscortMenuSmoke then -self.EscortMenuSmoke=MENU_GROUP:New(self.EscortClient:GetGroup(),"Smoke",self.EscortMenuReportNavigation,ESCORT._Smoke,self) -self.EscortMenuSmokeGreen=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release green smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Green,"Releasing green smoke!") -self.EscortMenuSmokeRed=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release red smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Red,"Releasing red smoke!") -self.EscortMenuSmokeWhite=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release white smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.White,"Releasing white smoke!") -self.EscortMenuSmokeOrange=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release orange smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Orange,"Releasing orange smoke!") -self.EscortMenuSmokeBlue=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release blue smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Blue,"Releasing blue smoke!") -end -end -return self -end -function ESCORT:MenuReportTargets(Seconds) -self:F({Seconds}) -if not self.EscortMenuReportNearbyTargets then -self.EscortMenuReportNearbyTargets=MENU_GROUP:New(self.EscortClient:GetGroup(),"Report targets",self.EscortMenu) -end -if not Seconds then -Seconds=30 -end -self.EscortMenuReportNearbyTargetsNow=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Report targets now!",self.EscortMenuReportNearbyTargets,ESCORT._ReportNearbyTargetsNow,self) -self.EscortMenuReportNearbyTargetsOn=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Report targets on",self.EscortMenuReportNearbyTargets,ESCORT._SwitchReportNearbyTargets,self,true) -self.EscortMenuReportNearbyTargetsOff=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Report targets off",self.EscortMenuReportNearbyTargets,ESCORT._SwitchReportNearbyTargets,self,false) -self.EscortMenuAttackNearbyTargets=MENU_GROUP:New(self.EscortClient:GetGroup(),"Attack targets",self.EscortMenu) -self.ReportTargetsScheduler,self.ReportTargetsSchedulerID=SCHEDULER:New(self,self._ReportTargetsScheduler,{},1,Seconds) -return self -end -function ESCORT:MenuAssistedAttack() -self:F() -self.EscortMenuTargetAssistance=MENU_GROUP:New(self.EscortClient:GetGroup(),"Request assistance from",self.EscortMenu) -return self -end -function ESCORT:MenuROE(MenuTextFormat) -self:F(MenuTextFormat) -if not self.EscortMenuROE then -self.EscortMenuROE=MENU_GROUP:New(self.EscortClient:GetGroup(),"ROE",self.EscortMenu) -if self.EscortGroup:OptionROEHoldFirePossible()then -self.EscortMenuROEHoldFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Hold Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEHoldFire(),"Holding weapons!") -end -if self.EscortGroup:OptionROEReturnFirePossible()then -self.EscortMenuROEReturnFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Return Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEReturnFire(),"Returning fire!") -end -if self.EscortGroup:OptionROEOpenFirePossible()then -self.EscortMenuROEOpenFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Open Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEOpenFire(),"Opening fire on designated targets!!") -end -if self.EscortGroup:OptionROEWeaponFreePossible()then -self.EscortMenuROEWeaponFree=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Weapon Free",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEWeaponFree(),"Opening fire on targets of opportunity!") -end -end -return self -end -function ESCORT:MenuEvasion(MenuTextFormat) -self:F(MenuTextFormat) -if self.EscortGroup:IsAir()then -if not self.EscortMenuEvasion then -self.EscortMenuEvasion=MENU_GROUP:New(self.EscortClient:GetGroup(),"Evasion",self.EscortMenu) -if self.EscortGroup:OptionROTNoReactionPossible()then -self.EscortMenuEvasionNoReaction=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Fight until death",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTNoReaction(),"Fighting until death!") -end -if self.EscortGroup:OptionROTPassiveDefensePossible()then -self.EscortMenuEvasionPassiveDefense=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Use flares, chaff and jammers",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTPassiveDefense(),"Defending using jammers, chaff and flares!") -end -if self.EscortGroup:OptionROTEvadeFirePossible()then -self.EscortMenuEvasionEvadeFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Evade enemy fire",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTEvadeFire(),"Evading on enemy fire!") -end -if self.EscortGroup:OptionROTVerticalPossible()then -self.EscortMenuOptionEvasionVertical=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Go below radar and evade fire",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTVertical(),"Evading on enemy fire with vertical manoeuvres!") -end -end -end -return self -end -function ESCORT:MenuResumeMission() -self:F() -if not self.EscortMenuResumeMission then -self.EscortMenuResumeMission=MENU_GROUP:New(self.EscortClient:GetGroup(),"Resume mission from",self.EscortMenu) -end -return self -end -function ESCORT:_HoldPosition(OrbitGroup,OrbitHeight,OrbitSeconds) -local EscortGroup=self.EscortGroup -local EscortClient=self.EscortClient -local OrbitUnit=OrbitGroup:GetUnit(1) -self.FollowScheduler:Stop(self.FollowSchedule) -local PointFrom={} -local GroupVec3=EscortGroup:GetUnit(1):GetVec3() -PointFrom={} -PointFrom.x=GroupVec3.x -PointFrom.y=GroupVec3.z -PointFrom.speed=250 -PointFrom.type=AI.Task.WaypointType.TURNING_POINT -PointFrom.alt=GroupVec3.y -PointFrom.alt_type=AI.Task.AltitudeType.BARO -local OrbitPoint=OrbitUnit:GetVec2() -local PointTo={} -PointTo.x=OrbitPoint.x -PointTo.y=OrbitPoint.y -PointTo.speed=250 -PointTo.type=AI.Task.WaypointType.TURNING_POINT -PointTo.alt=OrbitHeight -PointTo.alt_type=AI.Task.AltitudeType.BARO -PointTo.task=EscortGroup:TaskOrbitCircleAtVec2(OrbitPoint,OrbitHeight,0) -local Points={PointFrom,PointTo} -EscortGroup:OptionROEHoldFire() -EscortGroup:OptionROTPassiveDefense() -EscortGroup:SetTask(EscortGroup:TaskRoute(Points)) -EscortGroup:MessageToClient("Orbiting at location.",10,EscortClient) -end -function ESCORT:_JoinUpAndFollow(Distance) -local EscortGroup=self.EscortGroup -local EscortClient=self.EscortClient -self.Distance=Distance -self:JoinUpAndFollow(EscortGroup,EscortClient,self.Distance) -end -function ESCORT:JoinUpAndFollow(EscortGroup,EscortClient,Distance) -self:F({EscortGroup,EscortClient,Distance}) -self.FollowScheduler:Stop(self.FollowSchedule) -EscortGroup:OptionROEHoldFire() -EscortGroup:OptionROTPassiveDefense() -self.EscortMode=ESCORT.MODE.FOLLOW -self.CT1=0 -self.GT1=0 -self.FollowScheduler:Start(self.FollowSchedule) -EscortGroup:MessageToClient("Rejoining and Following at "..Distance.."!",30,EscortClient) -end -function ESCORT:_Flare(Color,Message) -local EscortGroup=self.EscortGroup -local EscortClient=self.EscortClient -EscortGroup:GetUnit(1):Flare(Color) -EscortGroup:MessageToClient(Message,10,EscortClient) -end -function ESCORT:_Smoke(Color,Message) -local EscortGroup=self.EscortGroup -local EscortClient=self.EscortClient -EscortGroup:GetUnit(1):Smoke(Color) -EscortGroup:MessageToClient(Message,10,EscortClient) -end -function ESCORT:_ReportNearbyTargetsNow() -local EscortGroup=self.EscortGroup -local EscortClient=self.EscortClient -self:_ReportTargetsScheduler() -end -function ESCORT:_SwitchReportNearbyTargets(ReportTargets) -local EscortGroup=self.EscortGroup -local EscortClient=self.EscortClient -self.ReportTargets=ReportTargets -if self.ReportTargets then -if not self.ReportTargetsScheduler then -self.ReportTargetsScheduler:Schedule(self,self._ReportTargetsScheduler,{},1,30) -end -else -self.ReportTargetsScheduler:Remove(self.ReportTargetsSchedulerID) -self.ReportTargetsScheduler=nil -end -end -function ESCORT:_ScanTargets(ScanDuration) -local EscortGroup=self.EscortGroup -local EscortClient=self.EscortClient -self.FollowScheduler:Stop(self.FollowSchedule) -if EscortGroup:IsHelicopter()then -EscortGroup:PushTask( -EscortGroup:TaskControlled( -EscortGroup:TaskOrbitCircle(200,20), -EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil) -),1) -elseif EscortGroup:IsAirPlane()then -EscortGroup:PushTask( -EscortGroup:TaskControlled( -EscortGroup:TaskOrbitCircle(1000,500), -EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil) -),1) -end -EscortGroup:MessageToClient("Scanning targets for "..ScanDuration.." seconds.",ScanDuration,EscortClient) -if self.EscortMode==ESCORT.MODE.FOLLOW then -self.FollowScheduler:Start(self.FollowSchedule) -end -end -function _Resume(EscortGroup) -env.info('_Resume') -local Escort=EscortGroup:GetState(EscortGroup,"Escort") -env.info("EscortMode = "..Escort.EscortMode) -if Escort.EscortMode==ESCORT.MODE.FOLLOW then -Escort:JoinUpAndFollow(EscortGroup,Escort.EscortClient,Escort.Distance) -end -end -function ESCORT:_AttackTarget(DetectedItem) -local EscortGroup=self.EscortGroup -self:F(EscortGroup) -local EscortClient=self.EscortClient -self.FollowScheduler:Stop(self.FollowSchedule) -if EscortGroup:IsAir()then -EscortGroup:OptionROEOpenFire() -EscortGroup:OptionROTPassiveDefense() -EscortGroup:SetState(EscortGroup,"Escort",self) -local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) -local Tasks={} -DetectedSet:ForEachUnit( -function(DetectedUnit,Tasks) -if DetectedUnit:IsAlive()then -Tasks[#Tasks+1]=EscortGroup:TaskAttackUnit(DetectedUnit) -end -end,Tasks -) -Tasks[#Tasks+1]=EscortGroup:TaskFunction("_Resume",{"''"}) -EscortGroup:SetTask( -EscortGroup:TaskCombo( -Tasks -),1 -) -else -local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) -local Tasks={} -DetectedSet:ForEachUnit( -function(DetectedUnit,Tasks) -if DetectedUnit:IsAlive()then -Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50) -end -end,Tasks -) -EscortGroup:SetTask( -EscortGroup:TaskCombo( -Tasks -),1 -) -end -EscortGroup:MessageToClient("Engaging Designated Unit!",10,EscortClient) -end -function ESCORT:_AssistTarget(EscortGroupAttack,DetectedItem) -local EscortGroup=self.EscortGroup -local EscortClient=self.EscortClient -self.FollowScheduler:Stop(self.FollowSchedule) -if EscortGroupAttack:IsAir()then -EscortGroupAttack:OptionROEOpenFire() -EscortGroupAttack:OptionROTVertical() -local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) -local Tasks={} -DetectedSet:ForEachUnit( -function(DetectedUnit,Tasks) -if DetectedUnit:IsAlive()then -Tasks[#Tasks+1]=EscortGroupAttack:TaskAttackUnit(DetectedUnit) -end -end,Tasks -) -Tasks[#Tasks+1]=EscortGroupAttack:TaskOrbitCircle(500,350) -EscortGroupAttack:SetTask( -EscortGroupAttack:TaskCombo( -Tasks -),1 -) -else -local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) -local Tasks={} -DetectedSet:ForEachUnit( -function(DetectedUnit,Tasks) -if DetectedUnit:IsAlive()then -Tasks[#Tasks+1]=EscortGroupAttack:TaskFireAtPoint(DetectedUnit:GetVec2(),50) -end -end,Tasks -) -EscortGroupAttack:SetTask( -EscortGroupAttack:TaskCombo( -Tasks -),1 -) -end -EscortGroupAttack:MessageToClient("Assisting with the destroying the enemy unit!",10,EscortClient) -end -function ESCORT:_ROE(EscortROEFunction,EscortROEMessage) -local EscortGroup=self.EscortGroup -local EscortClient=self.EscortClient -pcall(function()EscortROEFunction()end) -EscortGroup:MessageToClient(EscortROEMessage,10,EscortClient) -end -function ESCORT:_ROT(EscortROTFunction,EscortROTMessage) -local EscortGroup=self.EscortGroup -local EscortClient=self.EscortClient -pcall(function()EscortROTFunction()end) -EscortGroup:MessageToClient(EscortROTMessage,10,EscortClient) -end -function ESCORT:_ResumeMission(WayPoint) -local EscortGroup=self.EscortGroup -local EscortClient=self.EscortClient -self.FollowScheduler:Stop(self.FollowSchedule) -local WayPoints=EscortGroup:GetTaskRoute() -self:T(WayPoint,WayPoints) -for WayPointIgnore=1,WayPoint do -table.remove(WayPoints,1) -end -SCHEDULER:New(EscortGroup,EscortGroup.SetTask,{EscortGroup:TaskRoute(WayPoints)},1) -EscortGroup:MessageToClient("Resuming mission from waypoint "..WayPoint..".",10,EscortClient) -end -function ESCORT:RegisterRoute() -self:F() -local EscortGroup=self.EscortGroup -local TaskPoints=EscortGroup:GetTaskRoute() -self:T(TaskPoints) -return TaskPoints -end -function ESCORT:_FollowScheduler() -self:F({self.FollowDistance}) -self:T({self.EscortClient.UnitName,self.EscortGroup.GroupName}) -if self.EscortGroup:IsAlive()and self.EscortClient:IsAlive()then -local ClientUnit=self.EscortClient:GetClientGroupUnit() -local GroupUnit=self.EscortGroup:GetUnit(1) -local FollowDistance=self.FollowDistance -self:T({ClientUnit.UnitName,GroupUnit.UnitName}) -if self.CT1==0 and self.GT1==0 then -self.CV1=ClientUnit:GetVec3() -self:T({"self.CV1",self.CV1}) -self.CT1=timer.getTime() -self.GV1=GroupUnit:GetVec3() -self.GT1=timer.getTime() -else -local CT1=self.CT1 -local CT2=timer.getTime() -local CV1=self.CV1 -local CV2=ClientUnit:GetVec3() -self.CT1=CT2 -self.CV1=CV2 -local CD=((CV2.x-CV1.x)^2+(CV2.y-CV1.y)^2+(CV2.z-CV1.z)^2)^0.5 -local CT=CT2-CT1 -local CS=(3600/CT)*(CD/1000) -self:T2({"Client:",CS,CD,CT,CV2,CV1,CT2,CT1}) -local GT1=self.GT1 -local GT2=timer.getTime() -local GV1=self.GV1 -local GV2=GroupUnit:GetVec3() -self.GT1=GT2 -self.GV1=GV2 -local GD=((GV2.x-GV1.x)^2+(GV2.y-GV1.y)^2+(GV2.z-GV1.z)^2)^0.5 -local GT=GT2-GT1 -local GS=(3600/GT)*(GD/1000) -self:T2({"Group:",GS,GD,GT,GV2,GV1,GT2,GT1}) -local GV={x=GV2.x-CV2.x,y=GV2.y-CV2.y,z=GV2.z-CV2.z} -local GH2={x=GV2.x,y=CV2.y,z=GV2.z} -local alpha=math.atan2(GV.z,GV.x) -local CVI={x=CV2.x+FollowDistance*math.cos(alpha), -y=GH2.y, -z=CV2.z+FollowDistance*math.sin(alpha), -} -local DV={x=CV2.x-CVI.x,y=CV2.y-CVI.y,z=CV2.z-CVI.z} -local DVu={x=DV.x/FollowDistance,y=DV.y/FollowDistance,z=DV.z/FollowDistance} -local GDV={x=DVu.x*CS*8+CVI.x,y=CVI.y,z=DVu.z*CS*8+CVI.z} -if self.SmokeDirectionVector==true then -trigger.action.smoke(GDV,trigger.smokeColor.Red) -end -self:T2({"CV2:",CV2}) -self:T2({"CVI:",CVI}) -self:T2({"GDV:",GDV}) -local CatchUpDistance=((GDV.x-GV2.x)^2+(GDV.y-GV2.y)^2+(GDV.z-GV2.z)^2)^0.5 -local Time=10 -local CatchUpSpeed=(CatchUpDistance-(CS*8.4))/Time -local Speed=CS+CatchUpSpeed -if Speed<0 then -Speed=0 -end -self:T({"Client Speed, Escort Speed, Speed, FollowDistance, Time:",CS,GS,Speed,FollowDistance,Time}) -self.EscortGroup:RouteToVec3(GDV,Speed/3.6) -end -return true -end -return false -end -function ESCORT:_ReportTargetsScheduler() -self:F(self.EscortGroup:GetName()) -if self.EscortGroup:IsAlive()and self.EscortClient:IsAlive()then -if true then -local EscortGroupName=self.EscortGroup:GetName() -self.EscortMenuAttackNearbyTargets:RemoveSubMenus() -if self.EscortMenuTargetAssistance then -self.EscortMenuTargetAssistance:RemoveSubMenus() -end -local DetectedItems=self.Detection:GetDetectedItems() -self:F(DetectedItems) -local DetectedTargets=false -local DetectedMsgs={} -for ClientEscortGroupName,EscortGroupData in pairs(self.EscortClient._EscortGroups)do -local ClientEscortTargets=EscortGroupData.Detection -for DetectedItemIndex,DetectedItem in pairs(DetectedItems)do -self:F({DetectedItemIndex,DetectedItem}) -local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,EscortGroupData.EscortGroup,_DATABASE:GetPlayerSettings(self.EscortClient:GetPlayerName())) -if ClientEscortGroupName==EscortGroupName then -local DetectedMsg=DetectedItemReportSummary:Text("\n") -DetectedMsgs[#DetectedMsgs+1]=DetectedMsg -self:T(DetectedMsg) -MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(), -DetectedMsg, -self.EscortMenuAttackNearbyTargets, -ESCORT._AttackTarget, -self, -DetectedItem -) -else -if self.EscortMenuTargetAssistance then -local DetectedMsg=DetectedItemReportSummary:Text("\n") -self:T(DetectedMsg) -local MenuTargetAssistance=MENU_GROUP:New(self.EscortClient:GetGroup(),EscortGroupData.EscortName,self.EscortMenuTargetAssistance) -MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(), -DetectedMsg, -MenuTargetAssistance, -ESCORT._AssistTarget, -self, -EscortGroupData.EscortGroup, -DetectedItem -) -end -end -DetectedTargets=true -end -end -self:F(DetectedMsgs) -if DetectedTargets then -self.EscortGroup:MessageToClient("Reporting detected targets:\n"..table.concat(DetectedMsgs,"\n"),20,self.EscortClient) -else -self.EscortGroup:MessageToClient("No targets detected.",10,self.EscortClient) -end -return true -else -end -end -return false -end -MISSILETRAINER={ -ClassName="MISSILETRAINER", -TrackingMissiles={}, -} -function MISSILETRAINER._Alive(Client,self) -if self.Briefing then -Client:Message(self.Briefing,15,"Trainer") -end -if self.MenusOnOff==true then -Client:Message("Use the 'Radio Menu' -> 'Other (F10)' -> 'Missile Trainer' menu options to change the Missile Trainer settings (for all players).",15,"Trainer") -Client.MainMenu=MENU_GROUP:New(Client:GetGroup(),"Missile Trainer",nil) -Client.MenuMessages=MENU_GROUP:New(Client:GetGroup(),"Messages",Client.MainMenu) -Client.MenuOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Messages On",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=true}) -Client.MenuOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Messages Off",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=false}) -Client.MenuTracking=MENU_GROUP:New(Client:GetGroup(),"Tracking",Client.MainMenu) -Client.MenuTrackingToAll=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To All",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=true}) -Client.MenuTrackingToTarget=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To Target",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=false}) -Client.MenuTrackOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Tracking On",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=true}) -Client.MenuTrackOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Tracking Off",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=false}) -Client.MenuTrackIncrease=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Frequency Increase",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=-1}) -Client.MenuTrackDecrease=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Frequency Decrease",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=1}) -Client.MenuAlerts=MENU_GROUP:New(Client:GetGroup(),"Alerts",Client.MainMenu) -Client.MenuAlertsToAll=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To All",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=true}) -Client.MenuAlertsToTarget=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To Target",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=false}) -Client.MenuHitsOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Hits On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=true}) -Client.MenuHitsOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Hits Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=false}) -Client.MenuLaunchesOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Launches On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=true}) -Client.MenuLaunchesOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Launches Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=false}) -Client.MenuDetails=MENU_GROUP:New(Client:GetGroup(),"Details",Client.MainMenu) -Client.MenuDetailsDistanceOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Range On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=true}) -Client.MenuDetailsDistanceOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Range Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=false}) -Client.MenuDetailsBearingOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Bearing On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=true}) -Client.MenuDetailsBearingOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Bearing Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=false}) -Client.MenuDistance=MENU_GROUP:New(Client:GetGroup(),"Set distance to plane",Client.MainMenu) -Client.MenuDistance50=MENU_GROUP_COMMAND:New(Client:GetGroup(),"50 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=50/1000}) -Client.MenuDistance100=MENU_GROUP_COMMAND:New(Client:GetGroup(),"100 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=100/1000}) -Client.MenuDistance150=MENU_GROUP_COMMAND:New(Client:GetGroup(),"150 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=150/1000}) -Client.MenuDistance200=MENU_GROUP_COMMAND:New(Client:GetGroup(),"200 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=200/1000}) -else -if Client.MainMenu then -Client.MainMenu:Remove() -end -end -local ClientID=Client:GetID() -self:T(ClientID) -if not self.TrackingMissiles[ClientID]then -self.TrackingMissiles[ClientID]={} -end -self.TrackingMissiles[ClientID].Client=Client -if not self.TrackingMissiles[ClientID].MissileData then -self.TrackingMissiles[ClientID].MissileData={} -end -end -function MISSILETRAINER:New(Distance,Briefing) -local self=BASE:Inherit(self,BASE:New()) -self:F(Distance) -if Briefing then -self.Briefing=Briefing -end -self.Schedulers={} -self.SchedulerID=0 -self.MessageInterval=2 -self.MessageLastTime=timer.getTime() -self.Distance=Distance/1000 -self:HandleEvent(EVENTS.Shot) -self.DBClients=SET_CLIENT:New():FilterStart() -self.DBClients:ForEachClient( -function(Client) -self:F("ForEach:"..Client.UnitName) -Client:Alive(self._Alive,self) -end -) -self.MessagesOnOff=true -self.TrackingToAll=false -self.TrackingOnOff=true -self.TrackingFrequency=3 -self.AlertsToAll=true -self.AlertsHitsOnOff=true -self.AlertsLaunchesOnOff=true -self.DetailsRangeOnOff=true -self.DetailsBearingOnOff=true -self.MenusOnOff=true -self.TrackingMissiles={} -self.TrackingScheduler=SCHEDULER:New(self,self._TrackMissiles,{},0.5,0.05,0) -return self -end -function MISSILETRAINER:InitMessagesOnOff(MessagesOnOff) -self:F(MessagesOnOff) -self.MessagesOnOff=MessagesOnOff -if self.MessagesOnOff==true then -MESSAGE:New("Messages ON",15,"Menu"):ToAll() -else -MESSAGE:New("Messages OFF",15,"Menu"):ToAll() -end -return self -end -function MISSILETRAINER:InitTrackingToAll(TrackingToAll) -self:F(TrackingToAll) -self.TrackingToAll=TrackingToAll -if self.TrackingToAll==true then -MESSAGE:New("Missile tracking to all players ON",15,"Menu"):ToAll() -else -MESSAGE:New("Missile tracking to all players OFF",15,"Menu"):ToAll() -end -return self -end -function MISSILETRAINER:InitTrackingOnOff(TrackingOnOff) -self:F(TrackingOnOff) -self.TrackingOnOff=TrackingOnOff -if self.TrackingOnOff==true then -MESSAGE:New("Missile tracking ON",15,"Menu"):ToAll() -else -MESSAGE:New("Missile tracking OFF",15,"Menu"):ToAll() -end -return self -end -function MISSILETRAINER:InitTrackingFrequency(TrackingFrequency) -self:F(TrackingFrequency) -self.TrackingFrequency=self.TrackingFrequency+TrackingFrequency -if self.TrackingFrequency<0.5 then -self.TrackingFrequency=0.5 -end -if self.TrackingFrequency then -MESSAGE:New("Missile tracking frequency is "..self.TrackingFrequency.." seconds.",15,"Menu"):ToAll() -end -return self -end -function MISSILETRAINER:InitAlertsToAll(AlertsToAll) -self:F(AlertsToAll) -self.AlertsToAll=AlertsToAll -if self.AlertsToAll==true then -MESSAGE:New("Alerts to all players ON",15,"Menu"):ToAll() -else -MESSAGE:New("Alerts to all players OFF",15,"Menu"):ToAll() -end -return self -end -function MISSILETRAINER:InitAlertsHitsOnOff(AlertsHitsOnOff) -self:F(AlertsHitsOnOff) -self.AlertsHitsOnOff=AlertsHitsOnOff -if self.AlertsHitsOnOff==true then -MESSAGE:New("Alerts Hits ON",15,"Menu"):ToAll() -else -MESSAGE:New("Alerts Hits OFF",15,"Menu"):ToAll() -end -return self -end -function MISSILETRAINER:InitAlertsLaunchesOnOff(AlertsLaunchesOnOff) -self:F(AlertsLaunchesOnOff) -self.AlertsLaunchesOnOff=AlertsLaunchesOnOff -if self.AlertsLaunchesOnOff==true then -MESSAGE:New("Alerts Launches ON",15,"Menu"):ToAll() -else -MESSAGE:New("Alerts Launches OFF",15,"Menu"):ToAll() -end -return self -end -function MISSILETRAINER:InitRangeOnOff(DetailsRangeOnOff) -self:F(DetailsRangeOnOff) -self.DetailsRangeOnOff=DetailsRangeOnOff -if self.DetailsRangeOnOff==true then -MESSAGE:New("Range display ON",15,"Menu"):ToAll() -else -MESSAGE:New("Range display OFF",15,"Menu"):ToAll() -end -return self -end -function MISSILETRAINER:InitBearingOnOff(DetailsBearingOnOff) -self:F(DetailsBearingOnOff) -self.DetailsBearingOnOff=DetailsBearingOnOff -if self.DetailsBearingOnOff==true then -MESSAGE:New("Bearing display OFF",15,"Menu"):ToAll() -else -MESSAGE:New("Bearing display OFF",15,"Menu"):ToAll() -end -return self -end -function MISSILETRAINER:InitMenusOnOff(MenusOnOff) -self:F(MenusOnOff) -self.MenusOnOff=MenusOnOff -if self.MenusOnOff==true then -MESSAGE:New("Menus are ENABLED (only when a player rejoins a slot)",15,"Menu"):ToAll() -else -MESSAGE:New("Menus are DISABLED",15,"Menu"):ToAll() -end -return self -end -function MISSILETRAINER._MenuMessages(MenuParameters) -local self=MenuParameters.MenuSelf -if MenuParameters.MessagesOnOff~=nil then -self:InitMessagesOnOff(MenuParameters.MessagesOnOff) -end -if MenuParameters.TrackingToAll~=nil then -self:InitTrackingToAll(MenuParameters.TrackingToAll) -end -if MenuParameters.TrackingOnOff~=nil then -self:InitTrackingOnOff(MenuParameters.TrackingOnOff) -end -if MenuParameters.TrackingFrequency~=nil then -self:InitTrackingFrequency(MenuParameters.TrackingFrequency) -end -if MenuParameters.AlertsToAll~=nil then -self:InitAlertsToAll(MenuParameters.AlertsToAll) -end -if MenuParameters.AlertsHitsOnOff~=nil then -self:InitAlertsHitsOnOff(MenuParameters.AlertsHitsOnOff) -end -if MenuParameters.AlertsLaunchesOnOff~=nil then -self:InitAlertsLaunchesOnOff(MenuParameters.AlertsLaunchesOnOff) -end -if MenuParameters.DetailsRangeOnOff~=nil then -self:InitRangeOnOff(MenuParameters.DetailsRangeOnOff) -end -if MenuParameters.DetailsBearingOnOff~=nil then -self:InitBearingOnOff(MenuParameters.DetailsBearingOnOff) -end -if MenuParameters.Distance~=nil then -self.Distance=MenuParameters.Distance -MESSAGE:New("Hit detection distance set to "..(self.Distance*1000).." meters",15,"Menu"):ToAll() -end -end -function MISSILETRAINER:OnEventShot(EVentData) -self:F({EVentData}) -local TrainerSourceDCSUnit=EVentData.IniDCSUnit -local TrainerSourceDCSUnitName=EVentData.IniDCSUnitName -local TrainerWeapon=EVentData.Weapon -local TrainerWeaponName=EVentData.WeaponName -self:T("Missile Launched = "..TrainerWeaponName) -local TrainerTargetDCSUnit=TrainerWeapon:getTarget() -if TrainerTargetDCSUnit then -local TrainerTargetDCSUnitName=Unit.getName(TrainerTargetDCSUnit) -local TrainerTargetSkill=_DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill -self:T(TrainerTargetDCSUnitName) -local Client=self.DBClients:FindClient(TrainerTargetDCSUnitName) -if Client then -local TrainerSourceUnit=UNIT:Find(TrainerSourceDCSUnit) -local TrainerTargetUnit=UNIT:Find(TrainerTargetDCSUnit) -if self.MessagesOnOff==true and self.AlertsLaunchesOnOff==true then -local Message=MESSAGE:New( -string.format("%s launched a %s", -TrainerSourceUnit:GetTypeName(), -TrainerWeaponName -)..self:_AddRange(Client,TrainerWeapon)..self:_AddBearing(Client,TrainerWeapon),5,"Launch Alert") -if self.AlertsToAll then -Message:ToAll() -else -Message:ToClient(Client) -end -end -local ClientID=Client:GetID() -self:T(ClientID) -local MissileData={} -MissileData.TrainerSourceUnit=TrainerSourceUnit -MissileData.TrainerWeapon=TrainerWeapon -MissileData.TrainerTargetUnit=TrainerTargetUnit -MissileData.TrainerWeaponTypeName=TrainerWeapon:getTypeName() -MissileData.TrainerWeaponLaunched=true -table.insert(self.TrackingMissiles[ClientID].MissileData,MissileData) -end -else -if(TrainerWeapon:getTypeName()=="9M311")then -SCHEDULER:New(TrainerWeapon,TrainerWeapon.destroy,{},1) -else -end -end -end -function MISSILETRAINER:_AddRange(Client,TrainerWeapon) -local RangeText="" -if self.DetailsRangeOnOff then -local PositionMissile=TrainerWeapon:getPoint() -local TargetVec3=Client:GetVec3() -local Range=((PositionMissile.x-TargetVec3.x)^2+ -(PositionMissile.y-TargetVec3.y)^2+ -(PositionMissile.z-TargetVec3.z)^2 -)^0.5/1000 -RangeText=string.format(", at %4.2fkm",Range) -end -return RangeText -end -function MISSILETRAINER:_AddBearing(Client,TrainerWeapon) -local BearingText="" -if self.DetailsBearingOnOff then -local PositionMissile=TrainerWeapon:getPoint() -local TargetVec3=Client:GetVec3() -self:T2({TargetVec3,PositionMissile}) -local DirectionVector={x=PositionMissile.x-TargetVec3.x,y=PositionMissile.y-TargetVec3.y,z=PositionMissile.z-TargetVec3.z} -local DirectionRadians=math.atan2(DirectionVector.z,DirectionVector.x) -if DirectionRadians<0 then -DirectionRadians=DirectionRadians+2*math.pi -end -local DirectionDegrees=DirectionRadians*180/math.pi -BearingText=string.format(", %d degrees",DirectionDegrees) -end -return BearingText -end -function MISSILETRAINER:_TrackMissiles() -self:F2() -local ShowMessages=false -if self.MessagesOnOff and self.MessageLastTime+self.TrackingFrequency<=timer.getTime()then -self.MessageLastTime=timer.getTime() -ShowMessages=true -end -for ClientDataID,ClientData in pairs(self.TrackingMissiles)do -local Client=ClientData.Client -if Client and Client:IsAlive()then -for MissileDataID,MissileData in pairs(ClientData.MissileData)do -self:T3(MissileDataID) -local TrainerSourceUnit=MissileData.TrainerSourceUnit -local TrainerWeapon=MissileData.TrainerWeapon -local TrainerTargetUnit=MissileData.TrainerTargetUnit -local TrainerWeaponTypeName=MissileData.TrainerWeaponTypeName -local TrainerWeaponLaunched=MissileData.TrainerWeaponLaunched -if Client and Client:IsAlive()and TrainerSourceUnit and TrainerSourceUnit:IsAlive()and TrainerWeapon and TrainerWeapon:isExist()and TrainerTargetUnit and TrainerTargetUnit:IsAlive()then -local PositionMissile=TrainerWeapon:getPosition().p -local TargetVec3=Client:GetVec3() -local Distance=((PositionMissile.x-TargetVec3.x)^2+ -(PositionMissile.y-TargetVec3.y)^2+ -(PositionMissile.z-TargetVec3.z)^2 -)^0.5/1000 -if Distance<=self.Distance then -TrainerWeapon:destroy() -if self.MessagesOnOff==true and self.AlertsHitsOnOff==true then -self:T("killed") -local Message=MESSAGE:New( -string.format("%s launched by %s killed %s", -TrainerWeapon:getTypeName(), -TrainerSourceUnit:GetTypeName(), -TrainerTargetUnit:GetPlayerName() -),15,"Hit Alert") -if self.AlertsToAll==true then -Message:ToAll() -else -Message:ToClient(Client) -end -MissileData=nil -table.remove(ClientData.MissileData,MissileDataID) -self:T(ClientData.MissileData) -end -end -else -if not(TrainerWeapon and TrainerWeapon:isExist())then -if self.MessagesOnOff==true and self.AlertsLaunchesOnOff==true then -local Message=MESSAGE:New( -string.format("%s launched by %s self destructed!", -TrainerWeaponTypeName, -TrainerSourceUnit:GetTypeName() -),5,"Tracking") -if self.AlertsToAll==true then -Message:ToAll() -else -Message:ToClient(Client) -end -end -MissileData=nil -table.remove(ClientData.MissileData,MissileDataID) -self:T(ClientData.MissileData) -end -end -end -else -self.TrackingMissiles[ClientDataID]=nil -end -end -if ShowMessages==true and self.MessagesOnOff==true and self.TrackingOnOff==true then -for ClientDataID,ClientData in pairs(self.TrackingMissiles)do -local Client=ClientData.Client -ClientData.MessageToClient="" -ClientData.MessageToAll="" -for TrackingDataID,TrackingData in pairs(self.TrackingMissiles)do -for MissileDataID,MissileData in pairs(TrackingData.MissileData)do -local TrainerSourceUnit=MissileData.TrainerSourceUnit -local TrainerWeapon=MissileData.TrainerWeapon -local TrainerTargetUnit=MissileData.TrainerTargetUnit -local TrainerWeaponTypeName=MissileData.TrainerWeaponTypeName -local TrainerWeaponLaunched=MissileData.TrainerWeaponLaunched -if Client and Client:IsAlive()and TrainerSourceUnit and TrainerSourceUnit:IsAlive()and TrainerWeapon and TrainerWeapon:isExist()and TrainerTargetUnit and TrainerTargetUnit:IsAlive()then -if ShowMessages==true then -local TrackingTo -TrackingTo=string.format(" -> %s", -TrainerWeaponTypeName -) -if ClientDataID==TrackingDataID then -if ClientData.MessageToClient==""then -ClientData.MessageToClient="Missiles to You:\n" -end -ClientData.MessageToClient=ClientData.MessageToClient..TrackingTo..self:_AddRange(ClientData.Client,TrainerWeapon)..self:_AddBearing(ClientData.Client,TrainerWeapon).."\n" -else -if self.TrackingToAll==true then -if ClientData.MessageToAll==""then -ClientData.MessageToAll="Missiles to other Players:\n" -end -ClientData.MessageToAll=ClientData.MessageToAll..TrackingTo..self:_AddRange(ClientData.Client,TrainerWeapon)..self:_AddBearing(ClientData.Client,TrainerWeapon).." ( "..TrainerTargetUnit:GetPlayerName().." )\n" -end -end -end -end -end -end -if ClientData.MessageToClient~=""or ClientData.MessageToAll~=""then -local Message=MESSAGE:New(ClientData.MessageToClient..ClientData.MessageToAll,1,"Tracking"):ToClient(Client) -end -end -end -return true -end -ATC_GROUND={ -ClassName="ATC_GROUND", -SetClient=nil, -Airbases=nil, -AirbaseNames=nil, -} -function ATC_GROUND:New(Airbases,AirbaseList) -local self=BASE:Inherit(self,BASE:New()) -self:T({self.ClassName,Airbases}) -self.Airbases=Airbases -self.AirbaseList=AirbaseList -self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart() -for AirbaseID,Airbase in pairs(self.Airbases)do -if Airbase.ZoneBoundary then -Airbase.ZoneBoundary=ZONE_POLYGON_BASE:New("Boundary "..AirbaseID,Airbase.ZoneBoundary) -else -Airbase.ZoneBoundary=_DATABASE:FindAirbase(AirbaseID):GetZone() -end -Airbase.ZoneRunways={} -if Airbase.PointsRunways then -for PointsRunwayID,PointsRunway in pairs(Airbase.PointsRunways)do -Airbase.ZoneRunways[PointsRunwayID]=ZONE_POLYGON_BASE:New("Runway "..PointsRunwayID,PointsRunway) -end -end -Airbase.Monitor=self.AirbaseList and false or true -end -for AirbaseID,AirbaseName in pairs(self.AirbaseList or{})do -self.Airbases[AirbaseName].Monitor=true -end -self.SetClient:ForEachClient( -function(Client) -Client:SetState(self,"Speeding",false) -Client:SetState(self,"Warnings",0) -Client:SetState(self,"IsOffRunway",false) -Client:SetState(self,"OffRunwayWarnings",0) -Client:SetState(self,"Taxi",false) -end -) -SSB=USERFLAG:New("SSB") -SSB:Set(100) -return self -end -function ATC_GROUND:SmokeRunways(SmokeColor) -for AirbaseID,Airbase in pairs(self.Airbases)do -for PointsRunwayID,PointsRunway in pairs(Airbase.PointsRunways)do -Airbase.ZoneRunways[PointsRunwayID]:SmokeZone(SmokeColor) -end -end -end -function ATC_GROUND:SetKickSpeed(KickSpeed,Airbase) -if not Airbase then -self.KickSpeed=KickSpeed -else -self.Airbases[Airbase].KickSpeed=KickSpeed -end -return self -end -function ATC_GROUND:SetKickSpeedKmph(KickSpeed,Airbase) -self:SetKickSpeed(UTILS.KmphToMps(KickSpeed),Airbase) -return self -end -function ATC_GROUND:SetKickSpeedMiph(KickSpeedMiph,Airbase) -self:SetKickSpeed(UTILS.MiphToMps(KickSpeedMiph),Airbase) -return self -end -function ATC_GROUND:SetMaximumKickSpeed(MaximumKickSpeed,Airbase) -if not Airbase then -self.MaximumKickSpeed=MaximumKickSpeed -else -self.Airbases[Airbase].MaximumKickSpeed=MaximumKickSpeed -end -return self -end -function ATC_GROUND:SetMaximumKickSpeedKmph(MaximumKickSpeed,Airbase) -self:SetMaximumKickSpeed(UTILS.KmphToMps(MaximumKickSpeed),Airbase) -return self -end -function ATC_GROUND:SetMaximumKickSpeedMiph(MaximumKickSpeedMiph,Airbase) -self:SetMaximumKickSpeed(UTILS.MiphToMps(MaximumKickSpeedMiph),Airbase) -return self -end -function ATC_GROUND:_AirbaseMonitor() -self.SetClient:ForEachClient( -function(Client) -if Client:IsAlive()then -local IsOnGround=Client:InAir()==false -for AirbaseID,AirbaseMeta in pairs(self.Airbases)do -self:T(AirbaseID,AirbaseMeta.KickSpeed) -if AirbaseMeta.Monitor==true and Client:IsInZone(AirbaseMeta.ZoneBoundary)then -local NotInRunwayZone=true -for ZoneRunwayID,ZoneRunway in pairs(AirbaseMeta.ZoneRunways)do -NotInRunwayZone=(Client:IsNotInZone(ZoneRunway)==true)and NotInRunwayZone or false -end -if NotInRunwayZone then -if IsOnGround then -local Taxi=Client:GetState(self,"Taxi") -self:T(Taxi) -if Taxi==false then -local Velocity=VELOCITY:New(AirbaseMeta.KickSpeed or self.KickSpeed) -Client:Message("Welcome to "..AirbaseID..". The maximum taxiing speed is ".. -Velocity:ToString(),20,"ATC") -Client:SetState(self,"Taxi",true) -end -local Velocity=VELOCITY_POSITIONABLE:New(Client) -local IsAboveRunway=Client:IsAboveRunway() -self:T(IsAboveRunway,IsOnGround) -if IsOnGround then -local Speeding=false -if AirbaseMeta.MaximumKickSpeed then -if Velocity:Get()>AirbaseMeta.MaximumKickSpeed then -Speeding=true -end -else -if Velocity:Get()>self.MaximumKickSpeed then -Speeding=true -end -end -if Speeding==true then -MESSAGE:New("Penalty! Player "..Client:GetPlayerName().. -" has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll() -Client:Destroy() -Client:SetState(self,"Speeding",false) -Client:SetState(self,"Warnings",0) -end -end -if IsOnGround then -local Speeding=false -if AirbaseMeta.KickSpeed then -if Velocity:Get()>AirbaseMeta.KickSpeed then -Speeding=true -end -else -if Velocity:Get()>self.KickSpeed then -Speeding=true -end -end -if Speeding==true then -local IsSpeeding=Client:GetState(self,"Speeding") -if IsSpeeding==true then -local SpeedingWarnings=Client:GetState(self,"Warnings") -self:T(SpeedingWarnings) -if SpeedingWarnings<=3 then -Client:Message("Warning "..SpeedingWarnings.."/3! Airbase traffic rule violation! Slow down now! Your speed is ".. -Velocity:ToString(),5,"ATC") -Client:SetState(self,"Warnings",SpeedingWarnings+1) -else -MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll() -Client:Destroy() -Client:SetState(self,"Speeding",false) -Client:SetState(self,"Warnings",0) -end -else -Client:Message("Attention! You are speeding on the taxiway, slow down! Your speed is ".. -Velocity:ToString(),5,"ATC") -Client:SetState(self,"Speeding",true) -Client:SetState(self,"Warnings",1) -end -else -Client:SetState(self,"Speeding",false) -Client:SetState(self,"Warnings",0) -end -end -if IsOnGround and not IsAboveRunway then -local IsOffRunway=Client:GetState(self,"IsOffRunway") -if IsOffRunway==true then -local OffRunwayWarnings=Client:GetState(self,"OffRunwayWarnings") -self:T(OffRunwayWarnings) -if OffRunwayWarnings<=3 then -Client:Message("Warning "..OffRunwayWarnings.."/3! Airbase traffic rule violation! Get back on the taxi immediately!",5,"ATC") -Client:SetState(self,"OffRunwayWarnings",OffRunwayWarnings+1) -else -MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll() -Client:Destroy() -Client:SetState(self,"IsOffRunway",false) -Client:SetState(self,"OffRunwayWarnings",0) -end -else -Client:Message("Attention! You are off the taxiway. Get back on the taxiway immediately!",5,"ATC") -Client:SetState(self,"IsOffRunway",true) -Client:SetState(self,"OffRunwayWarnings",1) -end -else -Client:SetState(self,"IsOffRunway",false) -Client:SetState(self,"OffRunwayWarnings",0) -end -end -else -Client:SetState(self,"Speeding",false) -Client:SetState(self,"Warnings",0) -Client:SetState(self,"IsOffRunway",false) -Client:SetState(self,"OffRunwayWarnings",0) -local Taxi=Client:GetState(self,"Taxi") -if Taxi==true then -Client:Message("You have progressed to the runway ... Await take-off clearance ...",20,"ATC") -Client:SetState(self,"Taxi",false) -end -end -end -end -else -Client:SetState(self,"Taxi",false) -end -end -) -return true -end -ATC_GROUND_UNIVERSAL={ -ClassName="ATC_GROUND_UNIVERSAL", -Version="0.0.1", -SetClient=nil, -Airbases=nil, -AirbaseList=nil, -KickSpeed=nil, -} -function ATC_GROUND_UNIVERSAL:New(AirbaseList) -local self=BASE:Inherit(self,BASE:New()) -self:T({self.ClassName}) -self.Airbases={} -for _name,_ in pairs(_DATABASE.AIRBASES)do -self.Airbases[_name]={} -end -self.AirbaseList=AirbaseList -if not self.AirbaseList then -self.AirbaseList={} -for _name,_ in pairs(_DATABASE.AIRBASES)do -self.AirbaseList[_name]=_name -end -end -self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart() -for AirbaseID,Airbase in pairs(self.Airbases)do -if Airbase.ZoneBoundary then -Airbase.ZoneBoundary=ZONE_POLYGON_BASE:New("Boundary "..AirbaseID,Airbase.ZoneBoundary) -else -Airbase.ZoneBoundary=_DATABASE:FindAirbase(AirbaseID):GetZone() -end -Airbase.ZoneRunways=AIRBASE:FindByName(AirbaseID):GetRunways() -Airbase.Monitor=self.AirbaseList and false or true -end -for AirbaseID,AirbaseName in pairs(self.AirbaseList or{})do -self.Airbases[AirbaseName].Monitor=true -end -self.SetClient:ForEachClient( -function(Client) -Client:SetState(self,"Speeding",false) -Client:SetState(self,"Warnings",0) -Client:SetState(self,"IsOffRunway",false) -Client:SetState(self,"OffRunwayWarnings",0) -Client:SetState(self,"Taxi",false) -end -) -SSB=USERFLAG:New("SSB") -SSB:Set(100) -self.KickSpeed=UTILS.KnotsToMps(10) -self:SetMaximumKickSpeedMiph(30) -return self -end -function ATC_GROUND_UNIVERSAL:SetAirbaseBoundaries(Airbase,Zone) -self.Airbases[Airbase].ZoneBoundary=Zone -return self -end -function ATC_GROUND_UNIVERSAL:SmokeRunways(SmokeColor) -local SmokeColor=SmokeColor or SMOKECOLOR.Red -for AirbaseID,Airbase in pairs(self.Airbases)do -if Airbase.ZoneRunways then -for _,_runwaydata in pairs(Airbase.ZoneRunways)do -local runwaydata=_runwaydata -runwaydata.zone:SmokeZone(SmokeColor) -end -end -end -return self -end -function ATC_GROUND_UNIVERSAL:DrawRunways(Color) -local Color=Color or{1,0,0} -for AirbaseID,Airbase in pairs(self.Airbases)do -if Airbase.ZoneRunways then -for _,_runwaydata in pairs(Airbase.ZoneRunways)do -local runwaydata=_runwaydata -runwaydata.zone:DrawZone(-1,Color) -end -end -end -return self -end -function ATC_GROUND_UNIVERSAL:DrawBoundaries(Color) -local Color=Color or{1,0,0} -for AirbaseID,Airbase in pairs(self.Airbases)do -if Airbase.ZoneBoundary then -Airbase.ZoneBoundary:DrawZone(-1,Color) -end -end -return self -end -function ATC_GROUND_UNIVERSAL:SetKickSpeed(KickSpeed,Airbase) -if not Airbase then -self.KickSpeed=KickSpeed -else -self.Airbases[Airbase].KickSpeed=KickSpeed -end -return self -end -function ATC_GROUND_UNIVERSAL:SetKickSpeedKmph(KickSpeed,Airbase) -self:SetKickSpeed(UTILS.KmphToMps(KickSpeed),Airbase) -return self -end -function ATC_GROUND_UNIVERSAL:SetKickSpeedMiph(KickSpeedMiph,Airbase) -self:SetKickSpeed(UTILS.MiphToMps(KickSpeedMiph),Airbase) -return self -end -function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeed(MaximumKickSpeed,Airbase) -if not Airbase then -self.MaximumKickSpeed=MaximumKickSpeed -else -self.Airbases[Airbase].MaximumKickSpeed=MaximumKickSpeed -end -return self -end -function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeedKmph(MaximumKickSpeed,Airbase) -self:SetMaximumKickSpeed(UTILS.KmphToMps(MaximumKickSpeed),Airbase) -return self -end -function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeedMiph(MaximumKickSpeedMiph,Airbase) -self:SetMaximumKickSpeed(UTILS.MiphToMps(MaximumKickSpeedMiph),Airbase) -return self -end -function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() -self:I("_AirbaseMonitor") -self.SetClient:ForEachClient( -function(Client) -if Client:IsAlive()then -local IsOnGround=Client:InAir()==false -for AirbaseID,AirbaseMeta in pairs(self.Airbases)do -self:T(AirbaseID,AirbaseMeta.KickSpeed) -if AirbaseMeta.Monitor==true and Client:IsInZone(AirbaseMeta.ZoneBoundary)then -local NotInRunwayZone=true -if AirbaseMeta.ZoneRunways then -for _,_runwaydata in pairs(AirbaseMeta.ZoneRunways)do -local runwaydata=_runwaydata -NotInRunwayZone=(Client:IsNotInZone(_runwaydata.zone)==true)and NotInRunwayZone or false -end -end -if NotInRunwayZone then -if IsOnGround then -local Taxi=Client:GetState(self,"Taxi") -self:T(Taxi) -if Taxi==false then -local Velocity=VELOCITY:New(AirbaseMeta.KickSpeed or self.KickSpeed) -Client:Message("Welcome to "..AirbaseID..". The maximum taxiing speed is ".. -Velocity:ToString(),20,"ATC") -Client:SetState(self,"Taxi",true) -end -local Velocity=VELOCITY_POSITIONABLE:New(Client) -local IsAboveRunway=Client:IsAboveRunway() -self:T({IsAboveRunway,IsOnGround,Velocity:Get()}) -if IsOnGround then -local Speeding=false -if AirbaseMeta.MaximumKickSpeed then -if Velocity:Get()>AirbaseMeta.MaximumKickSpeed then -Speeding=true -end -else -if Velocity:Get()>self.MaximumKickSpeed then -Speeding=true -end -end -if Speeding==true then -MESSAGE:New("Penalty! Player "..Client:GetPlayerName().. -" has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll() -Client:Destroy() -Client:SetState(self,"Speeding",false) -Client:SetState(self,"Warnings",0) -end -end -if IsOnGround then -local Speeding=false -if AirbaseMeta.KickSpeed then -if Velocity:Get()>AirbaseMeta.KickSpeed then -Speeding=true -end -else -if Velocity:Get()>self.KickSpeed then -Speeding=true -end -end -if Speeding==true then -local IsSpeeding=Client:GetState(self,"Speeding") -if IsSpeeding==true then -local SpeedingWarnings=Client:GetState(self,"Warnings") -self:T(SpeedingWarnings) -if SpeedingWarnings<=3 then -Client:Message("Warning "..SpeedingWarnings.."/3! Airbase traffic rule violation! Slow down now! Your speed is ".. -Velocity:ToString(),5,"ATC") -Client:SetState(self,"Warnings",SpeedingWarnings+1) -else -MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll() -Client:Destroy() -Client:SetState(self,"Speeding",false) -Client:SetState(self,"Warnings",0) -end -else -Client:Message("Attention! You are speeding on the taxiway, slow down! Your speed is ".. -Velocity:ToString(),5,"ATC") -Client:SetState(self,"Speeding",true) -Client:SetState(self,"Warnings",1) -end -else -Client:SetState(self,"Speeding",false) -Client:SetState(self,"Warnings",0) -end -end -if IsOnGround and not IsAboveRunway then -local IsOffRunway=Client:GetState(self,"IsOffRunway") -if IsOffRunway==true then -local OffRunwayWarnings=Client:GetState(self,"OffRunwayWarnings") -self:T(OffRunwayWarnings) -if OffRunwayWarnings<=3 then -Client:Message("Warning "..OffRunwayWarnings.."/3! Airbase traffic rule violation! Get back on the taxi immediately!",5,"ATC") -Client:SetState(self,"OffRunwayWarnings",OffRunwayWarnings+1) -else -MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll() -Client:Destroy() -Client:SetState(self,"IsOffRunway",false) -Client:SetState(self,"OffRunwayWarnings",0) -end -else -Client:Message("Attention! You are off the taxiway. Get back on the taxiway immediately!",5,"ATC") -Client:SetState(self,"IsOffRunway",true) -Client:SetState(self,"OffRunwayWarnings",1) -end -else -Client:SetState(self,"IsOffRunway",false) -Client:SetState(self,"OffRunwayWarnings",0) -end -end -else -Client:SetState(self,"Speeding",false) -Client:SetState(self,"Warnings",0) -Client:SetState(self,"IsOffRunway",false) -Client:SetState(self,"OffRunwayWarnings",0) -local Taxi=Client:GetState(self,"Taxi") -if Taxi==true then -Client:Message("You have progressed to the runway ... Await take-off clearance ...",20,"ATC") -Client:SetState(self,"Taxi",false) -end -end -end -end -else -Client:SetState(self,"Taxi",false) -end -end -) -return true -end -function ATC_GROUND_UNIVERSAL:Start(RepeatScanSeconds) -RepeatScanSeconds=RepeatScanSeconds or 0.05 -self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds) -return self -end -ATC_GROUND_CAUCASUS={ -ClassName="ATC_GROUND_CAUCASUS", -} -function ATC_GROUND_CAUCASUS:New(AirbaseNames) -local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames)) -self:SetKickSpeedKmph(50) -self:SetMaximumKickSpeedKmph(150) -return self -end -function ATC_GROUND_CAUCASUS:Start(RepeatScanSeconds) -RepeatScanSeconds=RepeatScanSeconds or 0.05 -self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds) -end -ATC_GROUND_NEVADA={ -ClassName="ATC_GROUND_NEVADA", -} -function ATC_GROUND_NEVADA:New(AirbaseNames) -local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames)) -self:SetKickSpeedKmph(50) -self:SetMaximumKickSpeedKmph(150) -return self -end -function ATC_GROUND_NEVADA:Start(RepeatScanSeconds) -RepeatScanSeconds=RepeatScanSeconds or 0.05 -self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds) -end -ATC_GROUND_NORMANDY={ -ClassName="ATC_GROUND_NORMANDY", -} -function ATC_GROUND_NORMANDY:New(AirbaseNames) -local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames)) -self:SetKickSpeedKmph(40) -self:SetMaximumKickSpeedKmph(100) -return self -end -function ATC_GROUND_NORMANDY:Start(RepeatScanSeconds) -RepeatScanSeconds=RepeatScanSeconds or 0.05 -self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds) -end -ATC_GROUND_PERSIANGULF={ -ClassName="ATC_GROUND_PERSIANGULF", -} -function ATC_GROUND_PERSIANGULF:New(AirbaseNames) -local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames)) -self:SetKickSpeedKmph(50) -self:SetMaximumKickSpeedKmph(150) -end -function ATC_GROUND_PERSIANGULF:Start(RepeatScanSeconds) -RepeatScanSeconds=RepeatScanSeconds or 0.05 -self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds) -end -ATC_GROUND_MARIANAISLANDS={ -ClassName="ATC_GROUND_MARIANAISLANDS", -} -function ATC_GROUND_MARIANAISLANDS:New(AirbaseNames) -local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames)) -self:SetKickSpeedKmph(50) -self:SetMaximumKickSpeedKmph(150) -return self -end -function ATC_GROUND_MARIANAISLANDS:Start(RepeatScanSeconds) -RepeatScanSeconds=RepeatScanSeconds or 0.05 -self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds) -end -do -DETECTION_BASE={ -ClassName="DETECTION_BASE", -DetectionSetGroup=nil, -DetectionRange=nil, -DetectedObjects={}, -DetectionRun=0, -DetectedObjectsIdentified={}, -DetectedItems={}, -DetectedItemsByIndex={}, -} -function DETECTION_BASE:New(DetectionSet) -local self=BASE:Inherit(self,FSM:New()) -self.DetectedItemCount=0 -self.DetectedItemMax=0 -self.DetectedItems={} -self.DetectionSet=DetectionSet -self.RefreshTimeInterval=30 -self:InitDetectVisual(nil) -self:InitDetectOptical(nil) -self:InitDetectRadar(nil) -self:InitDetectRWR(nil) -self:InitDetectIRST(nil) -self:InitDetectDLINK(nil) -self:FilterCategories({ -Unit.Category.AIRPLANE, -Unit.Category.GROUND_UNIT, -Unit.Category.HELICOPTER, -Unit.Category.SHIP, -Unit.Category.STRUCTURE -}) -self:SetFriendliesRange(6000) -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Detecting") -self:AddTransition("Detecting","Detect","Detecting") -self:AddTransition("Detecting","Detection","Detecting") -self:AddTransition("Detecting","Detected","Detecting") -self:AddTransition("Detecting","DetectedItem","Detecting") -self:AddTransition("*","Stop","Stopped") -return self -end -do -function DETECTION_BASE:onafterStart(From,Event,To) -self:__Detect(1) -end -function DETECTION_BASE:onafterDetect(From,Event,To) -local DetectDelay=0.1 -self.DetectionCount=0 -self.DetectionRun=0 -self:UnIdentifyAllDetectedObjects() -local DetectionTimeStamp=timer.getTime() -for DetectionObjectName,DetectedObjectData in pairs(self.DetectedObjects)do -self.DetectedObjects[DetectionObjectName].IsDetected=false -self.DetectedObjects[DetectionObjectName].IsVisible=false -self.DetectedObjects[DetectionObjectName].KnowDistance=nil -self.DetectedObjects[DetectionObjectName].LastTime=nil -self.DetectedObjects[DetectionObjectName].LastPos=nil -self.DetectedObjects[DetectionObjectName].LastVelocity=nil -self.DetectedObjects[DetectionObjectName].Distance=10000000 -end -self.DetectionCount=self:CountAliveRecce() -local DetectionInterval=self.DetectionCount/(self.RefreshTimeInterval-1) -self:ForEachAliveRecce(function(DetectionGroup) -self:__Detection(DetectDelay,DetectionGroup,DetectionTimeStamp) -DetectDelay=DetectDelay+DetectionInterval -end) -self:__Detect(-self.RefreshTimeInterval) -end -function DETECTION_BASE:CountAliveRecce() -return self.DetectionSet:CountAlive() -end -function DETECTION_BASE:ForEachAliveRecce(IteratorFunction,...) -self:F2(arg) -self.DetectionSet:ForEachGroupAlive(IteratorFunction,arg) -return self -end -function DETECTION_BASE:onafterDetection(From,Event,To,Detection,DetectionTimeStamp) -self.DetectionRun=self.DetectionRun+1 -local HasDetectedObjects=false -if Detection and Detection:IsAlive()then -local DetectionGroupName=Detection:GetName() -local DetectionUnit=Detection:GetUnit(1) -local DetectedUnits={} -local DetectedTargets=Detection:GetDetectedTargets( -self.DetectVisual, -self.DetectOptical, -self.DetectRadar, -self.DetectIRST, -self.DetectRWR, -self.DetectDLINK -) -self:F({DetectedTargets=DetectedTargets}) -for DetectionObjectID,Detection in pairs(DetectedTargets)do -local DetectedObject=Detection.object -if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then -local DetectedObjectName=DetectedObject:getName() -if not self.DetectedObjects[DetectedObjectName]then -self.DetectedObjects[DetectedObjectName]=self.DetectedObjects[DetectedObjectName]or{} -self.DetectedObjects[DetectedObjectName].Name=DetectedObjectName -self.DetectedObjects[DetectedObjectName].Object=DetectedObject -end -end -end -for DetectionObjectName,DetectedObjectData in pairs(self.DetectedObjects)do -local DetectedObject=DetectedObjectData.Object -if DetectedObject:isExist()then -local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity=DetectionUnit:IsTargetDetected( -DetectedObject, -self.DetectVisual, -self.DetectOptical, -self.DetectRadar, -self.DetectIRST, -self.DetectRWR, -self.DetectDLINK -) -local DetectionAccepted=true -local DetectedObjectName=DetectedObject:getName() -local DetectedObjectType=DetectedObject:getTypeName() -local DetectedObjectVec3=DetectedObject:getPoint() -local DetectedObjectVec2={x=DetectedObjectVec3.x,y=DetectedObjectVec3.z} -local DetectionGroupVec3=Detection:GetVec3()or{x=0,y=0,z=0} -local DetectionGroupVec2={x=DetectionGroupVec3.x,y=DetectionGroupVec3.z} -local Distance=((DetectedObjectVec3.x-DetectionGroupVec3.x)^2+ -(DetectedObjectVec3.y-DetectionGroupVec3.y)^2+ -(DetectedObjectVec3.z-DetectionGroupVec3.z)^2 -)^0.5/1000 -local DetectedUnitCategory=DetectedObject:getDesc().category -DetectionAccepted=self._.FilterCategories[DetectedUnitCategory]~=nil and DetectionAccepted or false -if self.AcceptRange and Distance*1000>self.AcceptRange then -DetectionAccepted=false -end -if self.AcceptZones then -local AnyZoneDetection=false -for AcceptZoneID,AcceptZone in pairs(self.AcceptZones)do -local AcceptZone=AcceptZone -if AcceptZone:IsVec2InZone(DetectedObjectVec2)then -AnyZoneDetection=true -end -end -if not AnyZoneDetection then -DetectionAccepted=false -end -end -if self.RejectZones then -for RejectZoneID,RejectZone in pairs(self.RejectZones)do -local RejectZone=RejectZone -if RejectZone:IsVec2InZone(DetectedObjectVec2)==true then -DetectionAccepted=false -end -end -end -if self.RadarBlur then -MESSAGE:New("Radar Blur",10):ToLogIf(self.debug):ToAllIf(self.verbose) -local minheight=self.RadarBlurMinHeight or 250 -local thresheight=self.RadarBlurThresHeight or 90 -local thresblur=self.RadarBlurThresBlur or 85 -local dist=math.floor(Distance) -if dist<=self.RadarBlurClosing then -thresheight=(((dist*dist)/self.RadarBlurClosingSquare)*thresheight) -thresblur=(((dist*dist)/self.RadarBlurClosingSquare)*thresblur) -end -local fheight=math.floor(math.random(1,10000)/100) -local fblur=math.floor(math.random(1,10000)/100) -local unit=UNIT:FindByName(DetectedObjectName) -if unit and unit:IsAlive()then -local AGL=unit:GetAltitude(true) -MESSAGE:New("Unit "..DetectedObjectName.." is at "..math.floor(AGL).."m. Distance "..math.floor(Distance).."km.",10):ToLogIf(self.debug):ToAllIf(self.verbose) -MESSAGE:New(string.format("fheight = %d/%d | fblur = %d/%d",fheight,thresheight,fblur,thresblur),10):ToLogIf(self.debug):ToAllIf(self.verbose) -if fblur>thresblur then DetectionAccepted=false end -if AGL<=minheight and fheight0 and self.DetectionRun==self.DetectionCount then -for DetectedObjectName,DetectedObject in pairs(self.DetectedObjects)do -if self.DetectedObjects[DetectedObjectName].IsDetected==true and self.DetectedObjects[DetectedObjectName].DetectionTimeStamp+300<=DetectionTimeStamp then -self.DetectedObjects[DetectedObjectName].IsDetected=false -end -end -self:CreateDetectionItems() -for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do -self:UpdateDetectedItemDetection(DetectedItem) -self:CleanDetectionItem(DetectedItem,DetectedItemID) -if DetectedItem then -self:__DetectedItem(0.1,DetectedItem) -end -end -end -end -end -do -function DETECTION_BASE:CleanDetectionItem(DetectedItem,DetectedItemID) -local DetectedSet=DetectedItem.Set -if DetectedSet:Count()==0 then -self:RemoveDetectedItem(DetectedItemID) -end -return self -end -function DETECTION_BASE:ForgetDetectedUnit(UnitName) -local DetectedItems=self:GetDetectedItems() -for DetectedItemIndex,DetectedItem in pairs(DetectedItems)do -local DetectedSet=self:GetDetectedItemSet(DetectedItem) -if DetectedSet then -DetectedSet:RemoveUnitsByName(UnitName) -end -end -return self -end -function DETECTION_BASE:CreateDetectionItems() -self:F("Error, in DETECTION_BASE class...") -return self -end -end -do -function DETECTION_BASE:InitDetectVisual(DetectVisual) -self.DetectVisual=DetectVisual -return self -end -function DETECTION_BASE:InitDetectOptical(DetectOptical) -self:F2() -self.DetectOptical=DetectOptical -return self -end -function DETECTION_BASE:InitDetectRadar(DetectRadar) -self:F2() -self.DetectRadar=DetectRadar -return self -end -function DETECTION_BASE:InitDetectIRST(DetectIRST) -self:F2() -self.DetectIRST=DetectIRST -return self -end -function DETECTION_BASE:InitDetectRWR(DetectRWR) -self:F2() -self.DetectRWR=DetectRWR -return self -end -function DETECTION_BASE:InitDetectDLINK(DetectDLINK) -self:F2() -self.DetectDLINK=DetectDLINK -return self -end -end -do -function DETECTION_BASE:FilterCategories(FilterCategories) -self:F2() -self._.FilterCategories={} -if type(FilterCategories)=="table"then -for CategoryID,Category in pairs(FilterCategories)do -self._.FilterCategories[Category]=Category -end -else -self._.FilterCategories[FilterCategories]=FilterCategories -end -return self -end -function DETECTION_BASE:SetRadarBlur(minheight,thresheight,thresblur,closing) -self.RadarBlur=true -self.RadarBlurMinHeight=minheight or 250 -self.RadarBlurThresHeight=thresheight or 90 -self.RadarBlurThresBlur=thresblur or 85 -self.RadarBlurClosing=closing or 20 -self.RadarBlurClosingSquare=self.RadarBlurClosing*self.RadarBlurClosing -return self -end -end -do -function DETECTION_BASE:SetRefreshTimeInterval(RefreshTimeInterval) -self:F2() -self.RefreshTimeInterval=RefreshTimeInterval -return self -end -end -do -function DETECTION_BASE:SetFriendliesRange(FriendliesRange) -self:F2() -self.FriendliesRange=FriendliesRange -return self -end -end -do -function DETECTION_BASE:SetIntercept(Intercept,InterceptDelay) -self:F2() -self.Intercept=Intercept -self.InterceptDelay=InterceptDelay -return self -end -end -do -function DETECTION_BASE:SetAcceptRange(AcceptRange) -self:F2() -self.AcceptRange=AcceptRange -return self -end -function DETECTION_BASE:SetAcceptZones(AcceptZones) -self:F2() -if type(AcceptZones)=="table"then -if AcceptZones.ClassName and AcceptZones:IsInstanceOf(ZONE_BASE)then -self.AcceptZones={AcceptZones} -else -self.AcceptZones=AcceptZones -end -else -self:F({"AcceptZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object",AcceptZones}) -error() -end -return self -end -function DETECTION_BASE:SetRejectZones(RejectZones) -self:F2() -if type(RejectZones)=="table"then -if RejectZones.ClassName and RejectZones:IsInstanceOf(ZONE_BASE)then -self.RejectZones={RejectZones} -else -self.RejectZones=RejectZones -end -else -self:F({"RejectZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object",RejectZones}) -error() -end -return self -end -end -do -function DETECTION_BASE:SetDistanceProbability(DistanceProbability) -self:F2() -self.DistanceProbability=DistanceProbability -return self -end -function DETECTION_BASE:SetAlphaAngleProbability(AlphaAngleProbability) -self:F2() -self.AlphaAngleProbability=AlphaAngleProbability -return self -end -function DETECTION_BASE:SetZoneProbability(ZoneArray) -self:F2() -self.ZoneProbability=ZoneArray -return self -end -end -do -function DETECTION_BASE:AcceptChanges(DetectedItem) -DetectedItem.Changed=false -DetectedItem.Changes={} -return self -end -function DETECTION_BASE:AddChangeItem(DetectedItem,ChangeCode,ItemUnitType) -DetectedItem.Changed=true -local ID=DetectedItem.ID -DetectedItem.Changes=DetectedItem.Changes or{} -DetectedItem.Changes[ChangeCode]=DetectedItem.Changes[ChangeCode]or{} -DetectedItem.Changes[ChangeCode].ID=ID -DetectedItem.Changes[ChangeCode].ItemUnitType=ItemUnitType -self:F({"Change on Detected Item:",DetectedItemID=DetectedItem.ID,ChangeCode=ChangeCode,ItemUnitType=ItemUnitType}) -return self -end -function DETECTION_BASE:AddChangeUnit(DetectedItem,ChangeCode,ChangeUnitType) -DetectedItem.Changed=true -local ID=DetectedItem.ID -DetectedItem.Changes=DetectedItem.Changes or{} -DetectedItem.Changes[ChangeCode]=DetectedItem.Changes[ChangeCode]or{} -DetectedItem.Changes[ChangeCode][ChangeUnitType]=DetectedItem.Changes[ChangeCode][ChangeUnitType]or 0 -DetectedItem.Changes[ChangeCode][ChangeUnitType]=DetectedItem.Changes[ChangeCode][ChangeUnitType]+1 -DetectedItem.Changes[ChangeCode].ID=ID -self:F({"Change on Detected Unit:",DetectedItemID=DetectedItem.ID,ChangeCode=ChangeCode,ChangeUnitType=ChangeUnitType}) -return self -end -end -do -function DETECTION_BASE:SetFriendlyPrefixes(FriendlyPrefixes) -self.FriendlyPrefixes=self.FriendlyPrefixes or{} -if type(FriendlyPrefixes)~="table"then -FriendlyPrefixes={FriendlyPrefixes} -end -for PrefixID,Prefix in pairs(FriendlyPrefixes)do -self:F({FriendlyPrefix=Prefix}) -self.FriendlyPrefixes[Prefix]=Prefix -end -return self -end -function DETECTION_BASE:IsFriendliesNearBy(DetectedItem,Category) -return(DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category]~=nil)or false -end -function DETECTION_BASE:GetFriendliesNearBy(DetectedItem,Category) -return DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] -end -function DETECTION_BASE:IsFriendliesNearIntercept(DetectedItem) -return DetectedItem.FriendliesNearIntercept~=nil or false -end -function DETECTION_BASE:GetFriendliesNearIntercept(DetectedItem) -return DetectedItem.FriendliesNearIntercept -end -function DETECTION_BASE:GetFriendliesDistance(DetectedItem) -return DetectedItem.FriendliesDistance -end -function DETECTION_BASE:IsPlayersNearBy(DetectedItem) -return DetectedItem.PlayersNearBy~=nil -end -function DETECTION_BASE:GetPlayersNearBy(DetectedItem) -return DetectedItem.PlayersNearBy -end -function DETECTION_BASE:ReportFriendliesNearBy(TargetData) -local DetectedItem=TargetData.DetectedItem -local DetectedSet=TargetData.DetectedItem.Set -local DetectedUnit=DetectedSet:GetFirst() -DetectedItem.FriendliesNearBy=nil -if DetectedUnit and DetectedUnit:IsAlive()then -local DetectedUnitCoord=DetectedUnit:GetCoordinate() -local InterceptCoord=TargetData.InterceptCoord or DetectedUnitCoord -local SphereSearch={ -id=world.VolumeType.SPHERE, -params={ -point=InterceptCoord:GetVec3(), -radius=self.FriendliesRange, -} -} -local FindNearByFriendlies=function(FoundDCSUnit,ReportGroupData) -local DetectedItem=ReportGroupData.DetectedItem -local DetectedSet=ReportGroupData.DetectedItem.Set -local DetectedUnit=DetectedSet:GetFirst() -local DetectedUnitCoord=DetectedUnit:GetCoordinate() -local InterceptCoord=ReportGroupData.InterceptCoord or DetectedUnitCoord -local ReportSetGroup=ReportGroupData.ReportSetGroup -local EnemyCoalition=DetectedUnit:GetCoalition() -local FoundUnitCoalition=FoundDCSUnit:getCoalition() -local FoundUnitCategory=FoundDCSUnit:getDesc().category -local FoundUnitName=FoundDCSUnit:getName() -local FoundUnitGroupName=FoundDCSUnit:getGroup():getName() -local EnemyUnitName=DetectedUnit:GetName() -local FoundUnitInReportSetGroup=ReportSetGroup:FindGroup(FoundUnitGroupName)~=nil -if FoundUnitInReportSetGroup==true then -for PrefixID,Prefix in pairs(self.FriendlyPrefixes or{})do -if string.find(FoundUnitName,Prefix:gsub("-","%%-"),1)then -FoundUnitInReportSetGroup=false -break -end -end -end -if FoundUnitCoalition~=EnemyCoalition and FoundUnitInReportSetGroup==false then -local FriendlyUnit=UNIT:Find(FoundDCSUnit) -local FriendlyUnitName=FriendlyUnit:GetName() -local FriendlyUnitCategory=FriendlyUnit:GetDesc().category -DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{} -DetectedItem.FriendliesNearBy[FoundUnitCategory]=DetectedItem.FriendliesNearBy[FoundUnitCategory]or{} -DetectedItem.FriendliesNearBy[FoundUnitCategory][FriendlyUnitName]=FriendlyUnit -local Distance=DetectedUnitCoord:Get2DDistance(FriendlyUnit:GetCoordinate()) -DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{} -DetectedItem.FriendliesDistance[Distance]=FriendlyUnit -return true -end -return true -end -world.searchObjects(Object.Category.UNIT,SphereSearch,FindNearByFriendlies,TargetData) -DetectedItem.PlayersNearBy=nil -_DATABASE:ForEachPlayer( -function(PlayerUnitName) -local PlayerUnit=UNIT:FindByName(PlayerUnitName) -if PlayerUnit and PlayerUnit:IsAlive()then -local coord=PlayerUnit:GetCoordinate() -if coord and coord:IsInRadius(DetectedUnitCoord,self.FriendliesRange)then -local PlayerUnitCategory=PlayerUnit:GetDesc().category -if(not self.FriendliesCategory)or(self.FriendliesCategory and(self.FriendliesCategory==PlayerUnitCategory))then -local PlayerUnitName=PlayerUnit:GetName() -DetectedItem.PlayersNearBy=DetectedItem.PlayersNearBy or{} -DetectedItem.PlayersNearBy[PlayerUnitName]=PlayerUnit -DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{} -DetectedItem.FriendliesNearBy[PlayerUnitCategory]=DetectedItem.FriendliesNearBy[PlayerUnitCategory]or{} -DetectedItem.FriendliesNearBy[PlayerUnitCategory][PlayerUnitName]=PlayerUnit -local Distance=DetectedUnitCoord:Get2DDistance(PlayerUnit:GetCoordinate()) -DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{} -DetectedItem.FriendliesDistance[Distance]=PlayerUnit -end -end -end -end) -end -self:F({Friendlies=DetectedItem.FriendliesNearBy,Players=DetectedItem.PlayersNearBy}) -end -end -function DETECTION_BASE:IsDetectedObjectIdentified(DetectedObject) -local DetectedObjectName=DetectedObject.Name -if DetectedObjectName then -local DetectedObjectIdentified=self.DetectedObjectsIdentified[DetectedObjectName]==true -return DetectedObjectIdentified -else -return nil -end -end -function DETECTION_BASE:IdentifyDetectedObject(DetectedObject) -local DetectedObjectName=DetectedObject.Name -self.DetectedObjectsIdentified[DetectedObjectName]=true -end -function DETECTION_BASE:UnIdentifyDetectedObject(DetectedObject) -local DetectedObjectName=DetectedObject.Name -self.DetectedObjectsIdentified[DetectedObjectName]=false -end -function DETECTION_BASE:UnIdentifyAllDetectedObjects() -self.DetectedObjectsIdentified={} -end -function DETECTION_BASE:GetDetectedObject(ObjectName) -self:F2({ObjectName=ObjectName}) -if ObjectName then -local DetectedObject=self.DetectedObjects[ObjectName] -if DetectedObject then -local DetectedUnit=UNIT:FindByName(ObjectName) -if DetectedUnit and DetectedUnit:IsAlive()then -if self:IsDetectedObjectIdentified(DetectedObject)==false then -return DetectedObject -end -end -end -end -return nil -end -function DETECTION_BASE:GetDetectedUnitTypeName(DetectedUnit) -if DetectedUnit and DetectedUnit:IsAlive()then -local DetectedUnitName=DetectedUnit:GetName() -local DetectedObject=self.DetectedObjects[DetectedUnitName] -if DetectedObject then -if DetectedObject.KnowType then -return DetectedUnit:GetTypeName() -else -return"Unknown" -end -else -return"Unknown" -end -else -return"Dead:"..DetectedUnit:GetName() -end -return"Undetected:"..DetectedUnit:GetName() -end -function DETECTION_BASE:AddDetectedItem(ItemPrefix,DetectedItemKey,Set) -local DetectedItem={} -self.DetectedItemCount=self.DetectedItemCount+1 -self.DetectedItemMax=self.DetectedItemMax+1 -DetectedItemKey=DetectedItemKey or self.DetectedItemMax -self.DetectedItems[DetectedItemKey]=DetectedItem -self.DetectedItemsByIndex[DetectedItemKey]=DetectedItem -DetectedItem.Index=DetectedItemKey -DetectedItem.Set=Set or SET_UNIT:New():FilterDeads():FilterCrashes() -DetectedItem.ItemID=ItemPrefix.."."..self.DetectedItemMax -DetectedItem.ID=self.DetectedItemMax -DetectedItem.Removed=false -if self.Locking then -self:LockDetectedItem(DetectedItem) -end -return DetectedItem -end -function DETECTION_BASE:AddDetectedItemZone(ItemPrefix,DetectedItemKey,Set,Zone) -self:F({ItemPrefix,DetectedItemKey,Set,Zone}) -local DetectedItem=self:AddDetectedItem(ItemPrefix,DetectedItemKey,Set) -DetectedItem.Zone=Zone -return DetectedItem -end -function DETECTION_BASE:RemoveDetectedItem(DetectedItemKey) -local DetectedItem=self.DetectedItems[DetectedItemKey] -if DetectedItem then -self.DetectedItemCount=self.DetectedItemCount-1 -local DetectedItemIndex=DetectedItem.Index -self.DetectedItemsByIndex[DetectedItemIndex]=nil -self.DetectedItems[DetectedItemKey]=nil -end -end -function DETECTION_BASE:GetDetectedItems() -return self.DetectedItems -end -function DETECTION_BASE:GetDetectedItemsByIndex() -return self.DetectedItemsByIndex -end -function DETECTION_BASE:GetDetectedItemsCount() -local DetectedCount=self.DetectedItemCount -return DetectedCount -end -function DETECTION_BASE:GetDetectedItemByKey(Key) -self:F({DetectedItems=self.DetectedItems}) -local DetectedItem=self.DetectedItems[Key] -if DetectedItem then -return DetectedItem -end -return nil -end -function DETECTION_BASE:GetDetectedItemByIndex(Index) -self:F({self.DetectedItemsByIndex}) -local DetectedItem=self.DetectedItemsByIndex[Index] -if DetectedItem then -return DetectedItem -end -return nil -end -function DETECTION_BASE:GetDetectedItemID(DetectedItem) -return DetectedItem and DetectedItem.ItemID or"" -end -function DETECTION_BASE:GetDetectedID(Index) -local DetectedItem=self.DetectedItemsByIndex[Index] -if DetectedItem then -return DetectedItem.ID -end -return"" -end -function DETECTION_BASE:GetDetectedItemSet(DetectedItem) -local DetectedSetUnit=DetectedItem and DetectedItem.Set -if DetectedSetUnit then -return DetectedSetUnit -end -return nil -end -function DETECTION_BASE:UpdateDetectedItemDetection(DetectedItem) -local IsDetected=false -for UnitName,UnitData in pairs(DetectedItem.Set:GetSet())do -local DetectedObject=self.DetectedObjects[UnitName] -self:F({UnitName=UnitName,IsDetected=DetectedObject.IsDetected}) -if DetectedObject.IsDetected then -IsDetected=true -break -end -end -self:F({IsDetected=DetectedItem.IsDetected}) -DetectedItem.IsDetected=IsDetected -return IsDetected -end -function DETECTION_BASE:IsDetectedItemDetected(DetectedItem) -return DetectedItem.IsDetected -end -do -function DETECTION_BASE:GetDetectedItemZone(DetectedItem) -local DetectedZone=DetectedItem and DetectedItem.Zone -if DetectedZone then -return DetectedZone -end -local Detected -return nil -end -end -function DETECTION_BASE:LockDetectedItems() -for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do -self:LockDetectedItem(DetectedItem) -end -self.Locking=true -return self -end -function DETECTION_BASE:UnlockDetectedItems() -for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do -self:UnlockDetectedItem(DetectedItem) -end -self.Locking=nil -return self -end -function DETECTION_BASE:IsDetectedItemLocked(DetectedItem) -return self.Locking and DetectedItem.Locked==true -end -function DETECTION_BASE:LockDetectedItem(DetectedItem) -DetectedItem.Locked=true -return self -end -function DETECTION_BASE:UnlockDetectedItem(DetectedItem) -DetectedItem.Locked=nil -return self -end -function DETECTION_BASE:SetDetectedItemCoordinate(DetectedItem,Coordinate,DetectedItemUnit) -self:F({Coordinate=Coordinate}) -if DetectedItem then -if DetectedItemUnit then -DetectedItem.Coordinate=Coordinate -DetectedItem.Coordinate:SetHeading(DetectedItemUnit:GetHeading()) -DetectedItem.Coordinate.y=DetectedItemUnit:GetAltitude() -DetectedItem.Coordinate:SetVelocity(DetectedItemUnit:GetVelocityMPS()) -end -end -end -function DETECTION_BASE:GetDetectedItemCoordinate(DetectedItem) -self:F({DetectedItem=DetectedItem}) -if DetectedItem then -return DetectedItem.Coordinate -end -return nil -end -function DETECTION_BASE:GetDetectedItemCoordinates() -local Coordinates={} -for DetectedItemID,DetectedItem in pairs(self:GetDetectedItems())do -Coordinates[DetectedItem]=self:GetDetectedItemCoordinate(DetectedItem) -end -return Coordinates -end -function DETECTION_BASE:SetDetectedItemThreatLevel(DetectedItem) -local DetectedSet=DetectedItem.Set -if DetectedItem then -DetectedItem.ThreatLevel,DetectedItem.ThreatText=DetectedSet:CalculateThreatLevelA2G() -end -end -function DETECTION_BASE:GetDetectedItemThreatLevel(DetectedItem) -self:F({DetectedItem=DetectedItem}) -if DetectedItem then -self:F({ThreatLevel=DetectedItem.ThreatLevel,ThreatText=DetectedItem.ThreatText}) -return DetectedItem.ThreatLevel or 0,DetectedItem.ThreatText or"" -end -return nil,"" -end -function DETECTION_BASE:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings) -self:F() -return nil -end -function DETECTION_BASE:DetectedReportDetailed(AttackGroup) -self:F() -return nil -end -function DETECTION_BASE:GetDetectionSet() -local DetectionSet=self.DetectionSet -return DetectionSet -end -function DETECTION_BASE:NearestRecce(DetectedItem) -local NearestRecce=nil -local DistanceRecce=1000000000 -for RecceGroupName,RecceGroup in pairs(self.DetectionSet:GetSet())do -if RecceGroup and RecceGroup:IsAlive()then -for RecceUnit,RecceUnit in pairs(RecceGroup:GetUnits())do -if RecceUnit:IsActive()then -local RecceUnitCoord=RecceUnit:GetCoordinate() -local Distance=RecceUnitCoord:Get2DDistance(self:GetDetectedItemCoordinate(DetectedItem)) -if Distance0 then -DetectedItemCoordText=DetectedItemCoordinate:ToStringA2A(AttackGroup,Settings) -else -DetectedItemCoordText=DetectedItemCoordinate:ToStringA2G(AttackGroup,Settings) -end -local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem) -local DetectedItemsCount=DetectedSet:Count() -local DetectedItemsTypes=DetectedSet:GetTypeNames() -local Report=REPORT:New() -Report:Add(DetectedItemID..", "..DetectedItemCoordText) -Report:Add(string.format("Threat: [%s%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G))) -Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemsTypes)) -return Report -end -return nil -end -function DETECTION_AREAS:DetectedReportDetailed(AttackGroup) -self:F() -local Report=REPORT:New() -for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do -local DetectedItem=DetectedItem -local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup) -Report:SetTitle("Detected areas:") -Report:Add(ReportSummary:Text()) -end -local ReportText=Report:Text() -return ReportText -end -function DETECTION_AREAS:CalculateIntercept(DetectedItem) -local DetectedCoord=DetectedItem.Coordinate -local DetectedSpeed=DetectedCoord:GetVelocity() -local DetectedHeading=DetectedCoord:GetHeading() -if self.Intercept then -local DetectedSet=DetectedItem.Set -local TranslateDistance=DetectedSpeed*self.InterceptDelay -local InterceptCoord=DetectedCoord:Translate(TranslateDistance,DetectedHeading) -DetectedItem.InterceptCoord=InterceptCoord -else -DetectedItem.InterceptCoord=DetectedCoord -end -end -function DETECTION_AREAS:SmokeDetectedUnits() -self:F2() -self._SmokeDetectedUnits=true -return self -end -function DETECTION_AREAS:FlareDetectedUnits() -self:F2() -self._FlareDetectedUnits=true -return self -end -function DETECTION_AREAS:SmokeDetectedZones() -self:F2() -self._SmokeDetectedZones=true -return self -end -function DETECTION_AREAS:FlareDetectedZones() -self:F2() -self._FlareDetectedZones=true -return self -end -function DETECTION_AREAS:BoundDetectedZones() -self:F2() -self._BoundDetectedZones=true -return self -end -function DETECTION_AREAS:GetChangeText(DetectedItem) -self:F(DetectedItem) -local MT={} -for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do -if ChangeCode=="AA"then -MT[#MT+1]="Detected new area "..ChangeData.ID..". The center target is a "..ChangeData.ItemUnitType.."." -end -if ChangeCode=="RAU"then -MT[#MT+1]="Changed area "..ChangeData.ID..". Removed the center target." -end -if ChangeCode=="AAU"then -MT[#MT+1]="Changed area "..ChangeData.ID..". The new center target is a "..ChangeData.ItemUnitType.."." -end -if ChangeCode=="RA"then -MT[#MT+1]="Removed old area "..ChangeData.ID..". No more targets in this area." -end -if ChangeCode=="AU"then -local MTUT={} -for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do -if ChangeUnitType~="ID"then -MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType -end -end -MT[#MT+1]="Detected for area "..ChangeData.ID.." new target(s) "..table.concat(MTUT,", ").."." -end -if ChangeCode=="RU"then -local MTUT={} -for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do -if ChangeUnitType~="ID"then -MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType -end -end -MT[#MT+1]="Removed for area "..ChangeData.ID.." invisible or destroyed target(s) "..table.concat(MTUT,", ").."." -end -end -return table.concat(MT,"\n") -end -function DETECTION_AREAS:CreateDetectionItems() -self:F("Checking Detected Items for new Detected Units ...") -for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do -local DetectedItem=DetectedItemData -if DetectedItem then -self:T2({"Detected Item ID: ",DetectedItemID}) -local DetectedSet=DetectedItem.Set -local AreaExists=false -self:T3({"Zone Center Unit:",DetectedItem.Zone.ZoneUNIT.UnitName}) -local DetectedZoneObject=self:GetDetectedObject(DetectedItem.Zone.ZoneUNIT.UnitName) -self:T3({"Detected Zone Object:",DetectedItem.Zone:GetName(),DetectedZoneObject}) -if DetectedZoneObject then -AreaExists=true -else -DetectedSet:RemoveUnitsByName(DetectedItem.Zone.ZoneUNIT.UnitName) -self:AddChangeItem(DetectedItem,'RAU',self:GetDetectedUnitTypeName(DetectedItem.Zone.ZoneUNIT)) -for DetectedUnitName,DetectedUnitData in pairs(DetectedSet:GetSet())do -local DetectedUnit=DetectedUnitData -local DetectedObject=self:GetDetectedObject(DetectedUnit.UnitName) -local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit) -if DetectedObject then -self:IdentifyDetectedObject(DetectedObject) -AreaExists=true -DetectedItem.Zone=ZONE_UNIT:New(DetectedUnit:GetName(),DetectedUnit,self.DetectionZoneRange) -self:AddChangeItem(DetectedItem,"AAU",DetectedUnitTypeName) -break -else -DetectedSet:Remove(DetectedUnitName) -self:AddChangeUnit(DetectedItem,"RU",DetectedUnitTypeName) -end -end -end -if AreaExists then -for DetectedUnitName,DetectedUnitData in pairs(DetectedSet:GetSet())do -local DetectedUnit=DetectedUnitData -local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit) -local DetectedObject=nil -if DetectedUnit:IsAlive()then -DetectedObject=self:GetDetectedObject(DetectedUnit:GetName()) -end -if DetectedObject then -if DetectedUnit:IsInZone(DetectedItem.Zone)then -self:IdentifyDetectedObject(DetectedObject) -DetectedSet:AddUnit(DetectedUnit) -else -DetectedSet:Remove(DetectedUnitName) -self:AddChangeUnit(DetectedItem,"RU",DetectedUnitTypeName) -end -else -self:AddChangeUnit(DetectedItem,"RU","destroyed target") -DetectedSet:Remove(DetectedUnitName) -end -end -else -self:RemoveDetectedItem(DetectedItemID) -self:AddChangeItem(DetectedItem,"RA") -end -end -end -for DetectedUnitName,DetectedObjectData in pairs(self.DetectedObjects)do -local DetectedObject=self:GetDetectedObject(DetectedUnitName) -if DetectedObject then -local DetectedUnit=UNIT:FindByName(DetectedUnitName) -local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit) -local AddedToDetectionArea=false -for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do -local DetectedItem=DetectedItemData -if DetectedItem then -local DetectedSet=DetectedItem.Set -if not self:IsDetectedObjectIdentified(DetectedObject)and DetectedUnit:IsInZone(DetectedItem.Zone)then -self:IdentifyDetectedObject(DetectedObject) -DetectedSet:AddUnit(DetectedUnit) -AddedToDetectionArea=true -self:AddChangeUnit(DetectedItem,"AU",DetectedUnitTypeName) -end -end -end -if AddedToDetectionArea==false then -local DetectedItem=self:AddDetectedItemZone("AREA",nil, -SET_UNIT:New():FilterDeads():FilterCrashes(), -ZONE_UNIT:New(DetectedUnitName,DetectedUnit,self.DetectionZoneRange) -) -DetectedItem.Set:AddUnit(DetectedUnit) -self:AddChangeItem(DetectedItem,"AA",DetectedUnitTypeName) -end -end -end -for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do -local DetectedItem=DetectedItemData -local DetectedSet=DetectedItem.Set -local DetectedFirstUnit=DetectedSet:GetFirst() -local DetectedZone=DetectedItem.Zone -local DetectedZoneCoord=DetectedZone:GetCoordinate() -self:SetDetectedItemCoordinate(DetectedItem,DetectedZoneCoord,DetectedFirstUnit) -self:CalculateIntercept(DetectedItem) -local OldFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) -self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSet}) -local NewFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) -if OldFriendliesNearbyGround~=NewFriendliesNearbyGround then -DetectedItem.Changed=true -end -self:SetDetectedItemThreatLevel(DetectedItem) -self:NearestRecce(DetectedItem) -if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then -DetectedZone.ZoneUNIT:SmokeRed() -end -DetectedSet:ForEachUnit( -function(DetectedUnit) -if DetectedUnit:IsAlive()then -if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then -DetectedUnit:FlareGreen() -end -if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then -DetectedUnit:SmokeGreen() -end -end -end) -if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then -DetectedZone:FlareZone(SMOKECOLOR.White,30,math.random(0,90)) -end -if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then -DetectedZone:SmokeZone(SMOKECOLOR.White,30) -end -if DETECTION_AREAS._BoundDetectedZones or self._BoundDetectedZones then -self.CountryID=DetectedSet:GetFirst():GetCountry() -DetectedZone:BoundZone(12,self.CountryID) -end -end -end -end -do -DETECTION_ZONES={ -ClassName="DETECTION_ZONES", -DetectionZoneRange=nil, -} -function DETECTION_ZONES:New(DetectionSetZone,DetectionCoalition) -local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetZone)) -self.DetectionSetZone=DetectionSetZone -self.DetectionCoalition=DetectionCoalition -self._SmokeDetectedUnits=false -self._FlareDetectedUnits=false -self._SmokeDetectedZones=false -self._FlareDetectedZones=false -self._BoundDetectedZones=false -return self -end -function DETECTION_ZONES:CountAliveRecce() -return self.DetectionSetZone:Count() -end -function DETECTION_ZONES:ForEachAliveRecce(IteratorFunction,...) -self:F2(arg) -self.DetectionSetZone:ForEachZone(IteratorFunction,arg) -return self -end -function DETECTION_ZONES:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings) -self:F({DetectedItem=DetectedItem}) -local DetectedItemID=self:GetDetectedItemID(DetectedItem) -if DetectedItem then -local DetectedSet=self:GetDetectedItemSet(DetectedItem) -local ReportSummaryItem -local DetectedZone=self:GetDetectedItemZone(DetectedItem) -local DetectedItemCoordinate=DetectedZone:GetCoordinate() -local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings) -local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem) -local DetectedItemsCount=DetectedSet:Count() -local DetectedItemsTypes=DetectedSet:GetTypeNames() -local Report=REPORT:New() -Report:Add(DetectedItemID..", "..DetectedItemCoordText) -Report:Add(string.format("Threat: [%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G))) -Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemsTypes)) -Report:Add(string.format("Detected: %s",DetectedItem.IsDetected and"yes"or"no")) -return Report -end -return nil -end -function DETECTION_ZONES:DetectedReportDetailed(AttackGroup) -self:F() -local Report=REPORT:New() -for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do -local DetectedItem=DetectedItem -local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup) -Report:SetTitle("Detected areas:") -Report:Add(ReportSummary:Text()) -end -local ReportText=Report:Text() -return ReportText -end -function DETECTION_ZONES:CalculateIntercept(DetectedItem) -local DetectedCoord=DetectedItem.Coordinate -DetectedItem.InterceptCoord=DetectedCoord -end -function DETECTION_ZONES:SmokeDetectedUnits() -self:F2() -self._SmokeDetectedUnits=true -return self -end -function DETECTION_ZONES:FlareDetectedUnits() -self:F2() -self._FlareDetectedUnits=true -return self -end -function DETECTION_ZONES:SmokeDetectedZones() -self:F2() -self._SmokeDetectedZones=true -return self -end -function DETECTION_ZONES:FlareDetectedZones() -self:F2() -self._FlareDetectedZones=true -return self -end -function DETECTION_ZONES:BoundDetectedZones() -self:F2() -self._BoundDetectedZones=true -return self -end -function DETECTION_ZONES:GetChangeText(DetectedItem) -self:F(DetectedItem) -local MT={} -for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do -if ChangeCode=="AA"then -MT[#MT+1]="Detected new area "..ChangeData.ID..". The center target is a "..ChangeData.ItemUnitType.."." -end -if ChangeCode=="RAU"then -MT[#MT+1]="Changed area "..ChangeData.ID..". Removed the center target." -end -if ChangeCode=="AAU"then -MT[#MT+1]="Changed area "..ChangeData.ID..". The new center target is a "..ChangeData.ItemUnitType.."." -end -if ChangeCode=="RA"then -MT[#MT+1]="Removed old area "..ChangeData.ID..". No more targets in this area." -end -if ChangeCode=="AU"then -local MTUT={} -for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do -if ChangeUnitType~="ID"then -MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType -end -end -MT[#MT+1]="Detected for area "..ChangeData.ID.." new target(s) "..table.concat(MTUT,", ").."." -end -if ChangeCode=="RU"then -local MTUT={} -for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do -if ChangeUnitType~="ID"then -MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType -end -end -MT[#MT+1]="Removed for area "..ChangeData.ID.." invisible or destroyed target(s) "..table.concat(MTUT,", ").."." -end -end -return table.concat(MT,"\n") -end -function DETECTION_ZONES:CreateDetectionItems() -self:F("Checking Detected Items for new Detected Units ...") -local DetectedUnits=SET_UNIT:New() -for ZoneName,DetectionZone in pairs(self.DetectionSetZone:GetSet())do -local DetectedItem=self:GetDetectedItemByKey(ZoneName) -if DetectedItem==nil then -DetectedItem=self:AddDetectedItemZone("ZONE",ZoneName,nil,DetectionZone) -end -local DetectedItemSetUnit=self:GetDetectedItemSet(DetectedItem) -DetectionZone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) -local ZoneUnits=DetectionZone:GetScannedUnits() -for DCSUnitID,DCSUnit in pairs(ZoneUnits)do -local UnitName=DCSUnit:getName() -local ZoneUnit=UNIT:FindByName(UnitName) -local ZoneUnitCoalition=ZoneUnit:GetCoalition() -if ZoneUnitCoalition==self.DetectionCoalition then -if DetectedItemSetUnit:FindUnit(UnitName)==nil and DetectedUnits:FindUnit(UnitName)==nil then -self:F("Adding "..UnitName) -DetectedItemSetUnit:AddUnit(ZoneUnit) -DetectedUnits:AddUnit(ZoneUnit) -end -end -end -end -for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do -local DetectedItem=DetectedItemData -local DetectedSet=self:GetDetectedItemSet(DetectedItem) -local DetectedFirstUnit=DetectedSet:GetFirst() -local DetectedZone=self:GetDetectedItemZone(DetectedItem) -local DetectedZoneCoord=DetectedZone:GetCoordinate() -self:SetDetectedItemCoordinate(DetectedItem,DetectedZoneCoord,DetectedFirstUnit) -self:CalculateIntercept(DetectedItem) -local OldFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) -self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSetGroup}) -local NewFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) -if OldFriendliesNearbyGround~=NewFriendliesNearbyGround then -DetectedItem.Changed=true -end -self:SetDetectedItemThreatLevel(DetectedItem) -if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then -DetectedZone:SmokeZone(SMOKECOLOR.Red,30) -end -DetectedSet:ForEachUnit( -function(DetectedUnit) -if DetectedUnit:IsAlive()then -if DETECTION_ZONES._FlareDetectedUnits or self._FlareDetectedUnits then -DetectedUnit:FlareGreen() -end -if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then -DetectedUnit:SmokeGreen() -end -end -end -) -if DETECTION_ZONES._FlareDetectedZones or self._FlareDetectedZones then -DetectedZone:FlareZone(SMOKECOLOR.White,30,math.random(0,90)) -end -if DETECTION_ZONES._SmokeDetectedZones or self._SmokeDetectedZones then -DetectedZone:SmokeZone(SMOKECOLOR.White,30) -end -if DETECTION_ZONES._BoundDetectedZones or self._BoundDetectedZones then -self.CountryID=DetectedSet:GetFirst():GetCountry() -DetectedZone:BoundZone(12,self.CountryID) -end -end -end -function DETECTION_ZONES:onafterDetection(From,Event,To,Detection,DetectionTimeStamp) -self.DetectionRun=self.DetectionRun+1 -if self.DetectionCount>0 and self.DetectionRun==self.DetectionCount then -self:CreateDetectionItems() -for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do -self:UpdateDetectedItemDetection(DetectedItem) -self:CleanDetectionItem(DetectedItem,DetectedItemID) -if DetectedItem then -self:__DetectedItem(0.1,DetectedItem) -end -end -self:__Detect(-self.RefreshTimeInterval) -end -end -function DETECTION_ZONES:UpdateDetectedItemDetection(DetectedItem) -local IsDetected=true -DetectedItem.IsDetected=true -return IsDetected -end -end -do -DESIGNATE={ -ClassName="DESIGNATE", -} -function DESIGNATE:New(CC,Detection,AttackSet,Mission) -local self=BASE:Inherit(self,FSM:New()) -self:F({Detection}) -self:SetStartState("Designating") -self:AddTransition("*","Detect","*") -self:AddTransition("*","LaseOn","Lasing") -self:AddTransition("Lasing","Lasing","Lasing") -self:AddTransition("*","LaseOff","Designate") -self:AddTransition("*","Smoke","*") -self:AddTransition("*","Illuminate","*") -self:AddTransition("*","DoneSmoking","*") -self:AddTransition("*","DoneIlluminating","*") -self:AddTransition("*","Status","*") -self.CC=CC -self.Detection=Detection -self.AttackSet=AttackSet -self.RecceSet=Detection:GetDetectionSet() -self.Recces={} -self.Designating={} -self:SetDesignateName() -self:SetLaseDuration() -self:SetFlashStatusMenu(false) -self:SetFlashDetectionMessages(true) -self:SetMission(Mission) -self:SetLaserCodes({1688,1130,4785,6547,1465,4578}) -self:SetAutoLase(false,false) -self:SetThreatLevelPrioritization(false) -self:SetMaximumDesignations(5) -self:SetMaximumDistanceDesignations(8000) -self:SetMaximumMarkings(2) -self:SetDesignateMenu() -self.LaserCodesUsed={} -self.MenuLaserCodes={} -self.Detection:__Start(2) -self:__Detect(-15) -self.MarkScheduler=SCHEDULER:New(self) -return self -end -function DESIGNATE:SetFlashStatusMenu(FlashMenu) -self.FlashStatusMenu={} -self.AttackSet:ForEachGroupAlive( -function(AttackGroup) -self.FlashStatusMenu[AttackGroup]=FlashMenu -end -) -return self -end -function DESIGNATE:SetFlashDetectionMessages(FlashDetectionMessage) -self.FlashDetectionMessage={} -self.AttackSet:ForEachGroupAlive( -function(AttackGroup) -self.FlashDetectionMessage[AttackGroup]=FlashDetectionMessage -end -) -return self -end -function DESIGNATE:SetMaximumDesignations(MaximumDesignations) -self.MaximumDesignations=MaximumDesignations -return self -end -function DESIGNATE:SetMaximumDistanceGroundDesignation(MaximumDistanceGroundDesignation) -self.MaximumDistanceGroundDesignation=MaximumDistanceGroundDesignation -return self -end -function DESIGNATE:SetMaximumDistanceAirDesignation(MaximumDistanceAirDesignation) -self.MaximumDistanceAirDesignation=MaximumDistanceAirDesignation -return self -end -function DESIGNATE:SetMaximumDistanceDesignations(MaximumDistanceDesignations) -self.MaximumDistanceDesignations=MaximumDistanceDesignations -return self -end -function DESIGNATE:SetMaximumMarkings(MaximumMarkings) -self.MaximumMarkings=MaximumMarkings -return self -end -function DESIGNATE:SetLaserCodes(LaserCodes) -self.LaserCodes=(type(LaserCodes)=="table")and LaserCodes or{LaserCodes} -self:F({LaserCodes=self.LaserCodes}) -self.LaserCodesUsed={} -return self -end -function DESIGNATE:AddMenuLaserCode(LaserCode,MenuText) -self.MenuLaserCodes[LaserCode]=MenuText -self:SetDesignateMenu() -return self -end -function DESIGNATE:RemoveMenuLaserCode(LaserCode) -self.MenuLaserCodes[LaserCode]=nil -self:SetDesignateMenu() -return self -end -function DESIGNATE:SetDesignateName(DesignateName) -self.DesignateName="Designation"..(DesignateName and(" for "..DesignateName)or"") -return self -end -function DESIGNATE:SetLaseDuration(LaseDuration) -self.LaseDuration=LaseDuration or 120 -return self -end -function DESIGNATE:GenerateLaserCodes() -self.LaserCodes={} -local function containsDigit(_number,_numberToFind) -local _thisNumber=_number -local _thisDigit=0 -while _thisNumber~=0 do -_thisDigit=_thisNumber%10 -_thisNumber=math.floor(_thisNumber/10) -if _thisDigit==_numberToFind then -return true -end -end -return false -end -local _code=1111 -local _count=1 -while _code<1777 and _count<30 do -while true do -_code=_code+1 -if not containsDigit(_code,8) -and not containsDigit(_code,9) -and not containsDigit(_code,0)then -self:T(_code) -table.insert(self.LaserCodes,_code) -break -end -end -_count=_count+1 -end -self.LaserCodesUsed={} -return self -end -function DESIGNATE:SetAutoLase(AutoLase,Message) -self.AutoLase=AutoLase or false -if Message then -local AutoLaseOnOff=(self.AutoLase==true)and"On"or"Off" -local CC=self.CC:GetPositionable() -if CC then -CC:MessageToSetGroup(self.DesignateName..": Auto Lase "..AutoLaseOnOff..".",15,self.AttackSet) -end -end -self:CoordinateLase() -self:SetDesignateMenu() -return self -end -function DESIGNATE:SetThreatLevelPrioritization(Prioritize) -self.ThreatLevelPrioritization=Prioritize -return self -end -function DESIGNATE:SetMission(Mission) -self.Mission=Mission -return self -end -function DESIGNATE:onafterDetect() -self:__Detect(-math.random(60)) -self:DesignationScope() -self:CoordinateLase() -self:SendStatus() -self:SetDesignateMenu() -return self -end -function DESIGNATE:DesignationScope() -local DetectedItems=self.Detection:GetDetectedItemsByIndex() -local DetectedItemCount=0 -for DesignateIndex,Designating in pairs(self.Designating)do -local DetectedItem=self.Detection:GetDetectedItemByIndex(DesignateIndex) -if DetectedItem then -local IsDetected=self.Detection:IsDetectedItemDetected(DetectedItem) -self:F({IsDetected=IsDetected}) -if IsDetected==false then -self:F("Removing") -self.Designating[DesignateIndex]=nil -self.AttackSet:ForEachGroupAlive( -function(AttackGroup) -if AttackGroup:IsAlive()==true then -local DetectionText=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup):Text(", ") -self.CC:GetPositionable():MessageToGroup("Targets out of LOS\n"..DetectionText,10,AttackGroup,self.DesignateName) -end -end -) -else -DetectedItemCount=DetectedItemCount+1 -end -else -self.Designating[DesignateIndex]=nil -end -end -if DetectedItemCount<5 then -for DesignateIndex,DetectedItem in pairs(DetectedItems)do -local IsDetected=self.Detection:IsDetectedItemDetected(DetectedItem) -if IsDetected==true then -self:F({DistanceRecce=DetectedItem.DistanceRecce}) -if DetectedItem.DistanceRecce<=self.MaximumDistanceDesignations then -if self.Designating[DesignateIndex]==nil then -self.AttackSet:ForEachGroupAlive( -function(AttackGroup) -if self.FlashDetectionMessage[AttackGroup]==true then -local DetectionText=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup):Text(", ") -self.CC:GetPositionable():MessageToGroup("Targets detected at \n"..DetectionText,10,AttackGroup,self.DesignateName) -end -end -) -self.Designating[DesignateIndex]="" -break -end -end -end -end -end -return self -end -function DESIGNATE:CoordinateLase() -local DetectedItems=self.Detection:GetDetectedItemsByIndex() -for DesignateIndex,Designating in pairs(self.Designating)do -local DetectedItem=DetectedItems[DesignateIndex] -if DetectedItem then -if self.AutoLase then -self:LaseOn(DesignateIndex,self.LaseDuration) -end -end -end -return self -end -function DESIGNATE:SendStatus(MenuAttackGroup) -self.AttackSet:ForEachGroupAlive( -function(AttackGroup) -if self.FlashStatusMenu[AttackGroup]or(MenuAttackGroup and(AttackGroup:GetName()==MenuAttackGroup:GetName()))then -local DetectedReport=REPORT:New("Targets ready for Designation:") -local DetectedItems=self.Detection:GetDetectedItemsByIndex() -for DesignateIndex,Designating in pairs(self.Designating)do -local DetectedItem=DetectedItems[DesignateIndex] -if DetectedItem then -local Report=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup,nil,true):Text(", ") -DetectedReport:Add(string.rep("-",40)) -DetectedReport:Add(" - "..Report) -if string.find(Designating,"L")then -DetectedReport:Add(" - ".."Lasing Targets") -end -if string.find(Designating,"S")then -DetectedReport:Add(" - ".."Smoking Targets") -end -if string.find(Designating,"I")then -DetectedReport:Add(" - ".."Illuminating Area") -end -end -end -local CC=self.CC:GetPositionable() -CC:MessageTypeToGroup(DetectedReport:Text("\n"),MESSAGE.Type.Information,AttackGroup,self.DesignateName) -local DesignationReport=REPORT:New("Marking Targets:") -self.RecceSet:ForEachGroupAlive( -function(RecceGroup) -local RecceUnits=RecceGroup:GetUnits() -for UnitID,RecceData in pairs(RecceUnits)do -local Recce=RecceData -if Recce:IsLasing()then -DesignationReport:Add(" - "..Recce:GetMessageText("Marking "..Recce:GetSpot().Target:GetTypeName().." with laser "..Recce:GetSpot().LaserCode..".")) -end -end -end -) -CC:MessageTypeToGroup(DesignationReport:Text(),MESSAGE.Type.Information,AttackGroup,self.DesignateName) -end -end -) -return self -end -function DESIGNATE:SetMenu(AttackGroup) -self.MenuDesignate=self.MenuDesignate or{} -local MissionMenu=nil -if self.Mission then -MissionMenu=self.Mission:GetMenu(AttackGroup) -end -local MenuTime=timer.getTime() -self.MenuDesignate[AttackGroup]=MENU_GROUP_DELAYED:New(AttackGroup,self.DesignateName,MissionMenu):SetTime(MenuTime):SetTag(self.DesignateName) -local MenuDesignate=self.MenuDesignate[AttackGroup] -if self.AutoLase then -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Auto Lase Off",MenuDesignate,self.MenuAutoLase,self,false):SetTime(MenuTime):SetTag(self.DesignateName) -else -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Auto Lase On",MenuDesignate,self.MenuAutoLase,self,true):SetTime(MenuTime):SetTag(self.DesignateName) -end -local StatusMenu=MENU_GROUP_DELAYED:New(AttackGroup,"Status",MenuDesignate):SetTime(MenuTime):SetTag(self.DesignateName) -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Report Status",StatusMenu,self.MenuStatus,self,AttackGroup):SetTime(MenuTime):SetTag(self.DesignateName) -if self.FlashStatusMenu[AttackGroup]then -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Flash Status Report Off",StatusMenu,self.MenuFlashStatus,self,AttackGroup,false):SetTime(MenuTime):SetTag(self.DesignateName) -else -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Flash Status Report On",StatusMenu,self.MenuFlashStatus,self,AttackGroup,true):SetTime(MenuTime):SetTag(self.DesignateName) -end -local DesignateCount=0 -for DesignateIndex,Designating in pairs(self.Designating)do -local DetectedItem=self.Detection:GetDetectedItemByIndex(DesignateIndex) -if DetectedItem then -local Coord=self.Detection:GetDetectedItemCoordinate(DetectedItem) -local ID=self.Detection:GetDetectedItemID(DetectedItem) -local MenuText=ID -if DetectedItem.DesignateMenuName then -MenuText=string.format("(%3s) %s",Designating,DetectedItem.DesignateMenuName) -else -MenuText=string.format("(%3s) %s",Designating,MenuText) -end -local DetectedMenu=MENU_GROUP_DELAYED:New(AttackGroup,MenuText,MenuDesignate):SetTime(MenuTime):SetTag(self.DesignateName) -if string.find(Designating,"L",1,true)==nil then -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Search other target",DetectedMenu,self.MenuForget,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName) -for LaserCode,MenuText in pairs(self.MenuLaserCodes)do -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,string.format(MenuText,LaserCode),DetectedMenu,self.MenuLaseCode,self,DesignateIndex,self.LaseDuration,LaserCode):SetTime(MenuTime):SetTag(self.DesignateName) -end -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Lase with random laser code(s)",DetectedMenu,self.MenuLaseOn,self,DesignateIndex,self.LaseDuration):SetTime(MenuTime):SetTag(self.DesignateName) -else -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Stop lasing",DetectedMenu,self.MenuLaseOff,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName) -end -if string.find(Designating,"S",1,true)==nil then -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke red",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Red):SetTime(MenuTime):SetTag(self.DesignateName) -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke blue",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Blue):SetTime(MenuTime):SetTag(self.DesignateName) -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke green",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Green):SetTime(MenuTime):SetTag(self.DesignateName) -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke white",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.White):SetTime(MenuTime):SetTag(self.DesignateName) -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke orange",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Orange):SetTime(MenuTime):SetTag(self.DesignateName) -end -if string.find(Designating,"I",1,true)==nil then -MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Illuminate",DetectedMenu,self.MenuIlluminate,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName) -end -end -DesignateCount=DesignateCount+1 -if DesignateCount>10 then -break -end -end -MenuDesignate:Remove(MenuTime,self.DesignateName) -MenuDesignate:Set() -end -function DESIGNATE:SetDesignateMenu() -self.AttackSet:Flush(self) -local Delay=1 -self.AttackSet:ForEachGroupAlive( -function(AttackGroup) -self:ScheduleOnce(Delay,self.SetMenu,self,AttackGroup) -Delay=Delay+1 -end -) -return self -end -function DESIGNATE:MenuStatus(AttackGroup) -self:F("Status") -self:SendStatus(AttackGroup) -end -function DESIGNATE:MenuFlashStatus(AttackGroup,Flash) -self:F("Flash Status") -self.FlashStatusMenu[AttackGroup]=Flash -self:SetDesignateMenu() -end -function DESIGNATE:MenuForget(Index) -self:F("Forget") -self.Designating[Index]="" -self:SetDesignateMenu() -end -function DESIGNATE:MenuAutoLase(AutoLase) -self:F("AutoLase") -self:SetAutoLase(AutoLase,true) -end -function DESIGNATE:MenuSmoke(Index,Color) -self:F("Designate through Smoke") -if string.find(self.Designating[Index],"S")==nil then -self.Designating[Index]=self.Designating[Index].."S" -end -self:Smoke(Index,Color) -self:SetDesignateMenu() -end -function DESIGNATE:MenuIlluminate(Index) -self:F("Designate through Illumination") -if string.find(self.Designating[Index],"I",1,true)==nil then -self.Designating[Index]=self.Designating[Index].."I" -end -self:__Illuminate(1,Index) -self:SetDesignateMenu() -end -function DESIGNATE:MenuLaseOn(Index,Duration) -self:F("Designate through Lase") -self:__LaseOn(1,Index,Duration) -self:SetDesignateMenu() -end -function DESIGNATE:MenuLaseCode(Index,Duration,LaserCode) -self:F("Designate through Lase using "..LaserCode) -self:__LaseOn(1,Index,Duration,LaserCode) -self:SetDesignateMenu() -end -function DESIGNATE:MenuLaseOff(Index,Duration) -self:F("Lasing off") -self.Designating[Index]=string.gsub(self.Designating[Index],"L","") -self:__LaseOff(1,Index) -self:SetDesignateMenu() -end -function DESIGNATE:onafterLaseOn(From,Event,To,Index,Duration,LaserCode) -if string.find(self.Designating[Index],"L",1,true)==nil then -self.Designating[Index]=self.Designating[Index].."L" -self.LaseStart=timer.getTime() -self.LaseDuration=Duration -self:Lasing(Index,Duration,LaserCode) -end -end -function DESIGNATE:onafterLasing(From,Event,To,Index,Duration,LaserCodeRequested) -local DetectedItem=self.Detection:GetDetectedItemByIndex(Index) -local TargetSetUnit=self.Detection:GetDetectedItemSet(DetectedItem) -local MarkingCount=0 -local MarkedTypes={} -TargetSetUnit:Flush(self) -for TargetUnit,RecceData in pairs(self.Recces)do -local Recce=RecceData -self:F({TargetUnit=TargetUnit,Recce=Recce:GetName()}) -if not Recce:IsLasing()then -local LaserCode=Recce:GetLaserCode() -self:F({ClearingLaserCode=LaserCode}) -self.LaserCodesUsed[LaserCode]=nil -self.Recces[TargetUnit]=nil -end -end -if LaserCodeRequested then -for TargetUnit,RecceData in pairs(self.Recces)do -local Recce=RecceData -self:F({TargetUnit=TargetUnit,Recce=Recce:GetName()}) -if Recce:IsLasing()then -Recce:LaseOff() -local LaserCode=Recce:GetLaserCode() -self:F({ClearingLaserCode=LaserCode}) -self.LaserCodesUsed[LaserCode]=nil -self.Recces[TargetUnit]=nil -break -end -end -end -if self.AutoLase or(not self.AutoLase and(self.LaseStart+Duration>=timer.getTime()))then -TargetSetUnit:ForEachUnitPerThreatLevel(10,0, -function(TargetUnit) -self:F({TargetUnit=TargetUnit:GetName()}) -if MarkingCount0 and self.takeoff~=RAT.wp.air then -self.takeoff=RAT.wp.air -self:E(RAT.id..string.format("ERROR: At least one zone defined as departure and takeoff is NOT set to air. Enabling air start for RAT group %s!",self.alias)) -end -if self.Ndeparture_Airports==0 and self.Ndeparture_Zone==0 then -self.random_departure=true -local text=string.format("No airports or zones found given in SetDeparture(). Enabling random departure airports for RAT group %s!",self.alias) -self:E(RAT.id.."ERROR: "..text) -MESSAGE:New(text,30):ToAll() -end -end -if not self.random_destination then -for _,name in pairs(self.destination_ports)do -if self:_AirportExists(name)then -self.Ndestination_Airports=self.Ndestination_Airports+1 -elseif self:_ZoneExists(name)then -self.Ndestination_Zones=self.Ndestination_Zones+1 -end -end -if self.Ndestination_Zones>0 and self.landing~=RAT.wp.air and not self.returnzone then -self.landing=RAT.wp.air -self.destinationzone=true -self:E(RAT.id.."ERROR: At least one zone defined as destination and landing is NOT set to air. Enabling destination zone!") -end -if self.Ndestination_Airports==0 and self.Ndestination_Zones==0 then -self.random_destination=true -local text="No airports or zones found given in SetDestination(). Enabling random destination airports!" -self:E(RAT.id.."ERROR: "..text) -MESSAGE:New(text,30):ToAll() -end -end -if self.destinationzone and self.returnzone then -self:E(RAT.id.."ERROR: Destination zone _and_ return to zone not possible! Disabling return to zone.") -self.returnzone=false -end -if self.returnzone and self.takeoff==RAT.wp.air then -self.landing=RAT.wp.air -end -if self.FLminuser then -self.FLminuser=math.min(self.FLminuser,self.aircraft.ceiling) -end -if self.FLmaxuser then -self.FLmaxuser=math.min(self.FLmaxuser,self.aircraft.ceiling) -end -if self.FLcruise then -self.FLcruise=math.min(self.FLcruise,self.aircraft.ceiling) -end -if self.FLminuser and self.FLmaxuser then -if self.FLminuser>self.FLmaxuser then -local min=self.FLminuser -local max=self.FLmaxuser -self.FLminuser=max -self.FLmaxuser=min -end -end -if self.FLminuser and self.FLcruiseself.FLmaxuser then -self.FLcruise=self.FLmaxuser -end -if self.uncontrolled then -self.takeoff=RAT.wp.cold -end -end -function RAT:SetCoalition(friendly) -self:F2(friendly) -if friendly:lower()=="sameonly"then -self.friendly=RAT.coal.sameonly -elseif friendly:lower()=="neutral"then -self.friendly=RAT.coal.neutral -else -self.friendly=RAT.coal.same -end -return self -end -function RAT:SetCoalitionAircraft(color) -self:F2(color) -if color:lower()=="blue"then -self.coalition=coalition.side.BLUE -if not self.country then -self.country=country.id.USA -end -elseif color:lower()=="red"then -self.coalition=coalition.side.RED -if not self.country then -self.country=country.id.RUSSIA -end -elseif color:lower()=="neutral"then -self.coalition=coalition.side.NEUTRAL -if not self.country then -self.country=country.id.SWITZERLAND -end -end -return self -end -function RAT:SetCountry(id) -self:F2(id) -self.country=id -return self -end -function RAT:SetTerminalType(termtype) -self:F2(termtype) -self.termtype=termtype -return self -end -function RAT:SetParkingScanRadius(radius) -self:F2(radius) -self.parkingscanradius=radius or 50 -return self -end -function RAT:SetParkingScanSceneryON() -self:F2() -self.parkingscanscenery=true -return self -end -function RAT:SetParkingScanSceneryOFF() -self:F2() -self.parkingscanscenery=false -return self -end -function RAT:SetParkingSpotSafeON() -self:F2() -self.parkingverysafe=true -return self -end -function RAT:SetParkingSpotSafeOFF() -self:F2() -self.parkingverysafe=false -return self -end -function RAT:SetDespawnAirOFF() -self.despawnair=false -return self -end -function RAT:SetTakeoff(type) -self:F2(type) -local _Type -if type:lower()=="takeoff-cold"or type:lower()=="cold"then -_Type=RAT.wp.cold -elseif type:lower()=="takeoff-hot"or type:lower()=="hot"then -_Type=RAT.wp.hot -elseif type:lower()=="takeoff-runway"or type:lower()=="runway"then -_Type=RAT.wp.runway -elseif type:lower()=="air"then -_Type=RAT.wp.air -else -_Type=RAT.wp.coldorhot -end -self.takeoff=_Type -return self -end -function RAT:SetTakeoffCold() -self.takeoff=RAT.wp.cold -return self -end -function RAT:SetTakeoffHot() -self.takeoff=RAT.wp.hot -return self -end -function RAT:SetTakeoffRunway() -self.takeoff=RAT.wp.runway -return self -end -function RAT:SetTakeoffColdOrHot() -self.takeoff=RAT.wp.coldorhot -return self -end -function RAT:SetTakeoffAir() -self.takeoff=RAT.wp.air -return self -end -function RAT:SetDeparture(departurenames) -self:F2(departurenames) -self.random_departure=false -local names -if type(departurenames)=="table"then -names=departurenames -elseif type(departurenames)=="string"then -names={departurenames} -else -self:E(RAT.id.."ERROR: Input parameter must be a string or a table in SetDeparture()!") -end -for _,name in pairs(names)do -if self:_AirportExists(name)then -table.insert(self.departure_ports,name) -elseif self:_ZoneExists(name)then -table.insert(self.departure_ports,name) -else -self:E(RAT.id.."ERROR: No departure airport or zone found with name "..name) -end -end -return self -end -function RAT:SetDestination(destinationnames) -self:F2(destinationnames) -self.random_destination=false -local names -if type(destinationnames)=="table"then -names=destinationnames -elseif type(destinationnames)=="string"then -names={destinationnames} -else -self:E(RAT.id.."ERROR: Input parameter must be a string or a table in SetDestination()!") -end -for _,name in pairs(names)do -if self:_AirportExists(name)then -table.insert(self.destination_ports,name) -elseif self:_ZoneExists(name)then -table.insert(self.destination_ports,name) -else -self:E(RAT.id.."ERROR: No destination airport or zone found with name "..name) -end -end -return self -end -function RAT:DestinationZone() -self:F2() -self.destinationzone=true -self.landing=RAT.wp.air -return self -end -function RAT:ReturnZone() -self:F2() -self.returnzone=true -return self -end -function RAT:SetDestinationsFromZone(zone) -self:F2(zone) -self.random_destination=false -self.destination_Azone=zone -return self -end -function RAT:SetDeparturesFromZone(zone) -self:F2(zone) -self.random_departure=false -self.departure_Azone=zone -return self -end -function RAT:AddFriendlyAirportsToDepartures() -self:F2() -self.addfriendlydepartures=true -return self -end -function RAT:AddFriendlyAirportsToDestinations() -self:F2() -self.addfriendlydestinations=true -return self -end -function RAT:ExcludedAirports(ports) -self:F2(ports) -if type(ports)=="string"then -self.excluded_ports={ports} -else -self.excluded_ports=ports -end -return self -end -function RAT:SetAISkill(skill) -self:F2(skill) -if skill:lower()=="average"then -self.skill="Average" -elseif skill:lower()=="good"then -self.skill="Good" -elseif skill:lower()=="excellent"then -self.skill="Excellent" -elseif skill:lower()=="random"then -self.skill="Random" -else -self.skill="High" -end -return self -end -function RAT:Livery(skins) -self:F2(skins) -if type(skins)=="string"then -self.livery={skins} -else -self.livery=skins -end -return self -end -function RAT:ChangeAircraft(actype) -self:F2(actype) -self.actype=actype -return self -end -function RAT:ContinueJourney() -self:F2() -self.continuejourney=true -self.commute=false -return self -end -function RAT:Commute(starshape) -self:F2() -self.commute=true -self.continuejourney=false -if starshape then -self.starshape=starshape -else -self.starshape=false -end -return self -end -function RAT:SetSpawnDelay(delay) -self:F2(delay) -delay=delay or 5 -self.spawndelay=math.max(0.5,delay) -return self -end -function RAT:SetSpawnInterval(interval) -self:F2(interval) -interval=interval or 5 -self.spawninterval=math.max(0.5,interval) -return self -end -function RAT:RespawnAfterLanding(delay) -self:F2(delay) -delay=delay or 180 -self.respawn_at_landing=true -delay=math.max(1.0,delay) -self.respawn_delay=delay -return self -end -function RAT:SetRespawnDelay(delay) -self:F2(delay) -delay=delay or 1.0 -delay=math.max(1.0,delay) -self.respawn_delay=delay -return self -end -function RAT:NoRespawn() -self:F2() -self.norespawn=true -return self -end -function RAT:SetMaxRespawnTriedWhenSpawnedOnRunway(n) -self:F2(n) -n=n or 3 -self.onrunwaymaxretry=n -return self -end -function RAT:RespawnAfterTakeoff() -self:F2() -self.respawn_after_takeoff=true -return self -end -function RAT:RespawnAfterCrashON() -self:F2() -self.respawn_after_crash=true -return self -end -function RAT:RespawnAfterCrashOFF() -self:F2() -self.respawn_after_crash=false -return self -end -function RAT:RespawnInAirAllowed() -self:F2() -self.respawn_inair=true -return self -end -function RAT:RespawnInAirNotAllowed() -self:F2() -self.respawn_inair=false -return self -end -function RAT:CheckOnRunway(switch,distance) -self:F2(switch) -if switch==nil then -switch=true -end -self.checkonrunway=switch -self.onrunwayradius=distance or 75 -return self -end -function RAT:CheckOnTop(switch,radius) -self:F2(switch) -if switch==nil then -switch=true -end -self.checkontop=switch -self.ontopradius=radius or 2 -return self -end -function RAT:ParkingSpotDB(switch) -self:E("RAT ParkingSpotDB function is obsolete and will be removed soon!") -return self -end -function RAT:RadioON() -self:F2() -self.radio=true -return self -end -function RAT:RadioOFF() -self:F2() -self.radio=false -return self -end -function RAT:RadioFrequency(frequency) -self:F2(frequency) -self.frequency=frequency -return self -end -function RAT:RadioModulation(modulation) -self:F2(modulation) -if modulation=="AM"then -self.modulation=radio.modulation.AM -elseif modulation=="FM"then -self.modulation=radio.modulation.FM -else -self.modulation=radio.modulation.AM -end -return self -end -function RAT:RadioMenuON() -self:F2() -self.f10menu=true -return self -end -function RAT:RadioMenuOFF() -self:F2() -self.f10menu=false -return self -end -function RAT:Invisible() -self:F2() -self.invisible=true -return self -end -function RAT:SetEPLRS(switch) -if switch==nil or switch==true then -self.eplrs=true -else -self.eplrs=false -end -return self -end -function RAT:Immortal() -self:F2() -self.immortal=true -return self -end -function RAT:Uncontrolled() -self:F2() -self.uncontrolled=true -return self -end -function RAT:ActivateUncontrolled(maxactivated,delay,delta,frand) -self:F2({max=maxactivated,delay=delay,delta=delta,rand=frand}) -self.activate_uncontrolled=true -self.activate_max=maxactivated or 1 -self.activate_delay=delay or 1 -self.activate_delta=delta or 1 -self.activate_frand=frand or 0 -self.activate_delay=math.max(self.activate_delay,1) -self.activate_delta=math.max(self.activate_delta,0) -self.activate_frand=math.max(self.activate_frand,0) -self.activate_frand=math.min(self.activate_frand,1) -return self -end -function RAT:TimeDestroyInactive(time) -self:F2(time) -time=time or self.Tinactive -time=math.max(time,60) -self.Tinactive=time -return self -end -function RAT:SetMaxCruiseSpeed(speed) -self:F2(speed) -self.Vcruisemax=speed/3.6 -return self -end -function RAT:SetClimbRate(rate) -self:F2(rate) -rate=rate or self.Vclimb -rate=math.max(rate,100) -rate=math.min(rate,15000) -self.Vclimb=rate -return self -end -function RAT:SetDescentAngle(angle) -self:F2(angle) -angle=angle or self.AlphaDescent -angle=math.max(angle,0.5) -angle=math.min(angle,50) -self.AlphaDescent=angle -return self -end -function RAT:SetROE(roe) -self:F2(roe) -if roe=="return"then -self.roe=RAT.ROE.returnfire -elseif roe=="free"then -self.roe=RAT.ROE.weaponfree -else -self.roe=RAT.ROE.weaponhold -end -return self -end -function RAT:SetROT(rot) -self:F2(rot) -if rot=="passive"then -self.rot=RAT.ROT.passive -elseif rot=="evade"then -self.rot=RAT.ROT.evade -else -self.rot=RAT.ROT.noreaction -end -return self -end -function RAT:MenuName(name) -self:F2(name) -self.SubMenuName=tostring(name) -return self -end -function RAT:EnableATC(switch) -self:F2(switch) -if switch==nil then -switch=true -end -self.ATCswitch=switch -return self -end -function RAT:ATC_Messages(switch) -self:F2(switch) -if switch==nil then -switch=true -end -RAT.ATC.messages=switch -return self -end -function RAT:ATC_Clearance(n) -self:F2(n) -RAT.ATC.Nclearance=n or 2 -return self -end -function RAT:ATC_Delay(time) -self:F2(time) -RAT.ATC.delay=time or 240 -return self -end -function RAT:SetMinDistance(dist) -self:F2(dist) -self.mindist=math.max(100,dist*1000) -return self -end -function RAT:SetMaxDistance(dist) -self:F2(dist) -self.maxdist=dist*1000 -return self -end -function RAT:_Debug(switch) -self:F2(switch) -if switch==nil then -switch=true -end -self.Debug=switch -return self -end -function RAT:Debugmode() -self:F2() -self.Debug=true -return self -end -function RAT:StatusReports(switch) -self:F2(switch) -if switch==nil then -switch=true -end -self.reportstatus=switch -return self -end -function RAT:PlaceMarkers(switch) -self:F2(switch) -if switch==nil then -switch=true -end -self.placemarkers=switch -return self -end -function RAT:SetFL(FL) -self:F2(FL) -FL=FL or self.FLcruise -FL=math.max(FL,0) -self.FLuser=FL*RAT.unit.FL2m -return self -end -function RAT:SetFLmax(FL) -self:F2(FL) -self.FLmaxuser=FL*RAT.unit.FL2m -return self -end -function RAT:SetMaxCruiseAltitude(alt) -self:F2(alt) -self.FLmaxuser=alt -return self -end -function RAT:SetFLmin(FL) -self:F2(FL) -self.FLminuser=FL*RAT.unit.FL2m -return self -end -function RAT:SetMinCruiseAltitude(alt) -self:F2(alt) -self.FLminuser=alt -return self -end -function RAT:SetFLcruise(FL) -self:F2(FL) -self.FLcruise=FL*RAT.unit.FL2m -return self -end -function RAT:SetCruiseAltitude(alt) -self:F2(alt) -self.FLcruise=alt -return self -end -function RAT:SetOnboardNum(tailnumprefix,zero) -self:F2({tailnumprefix=tailnumprefix,zero=zero}) -self.onboardnum=tailnumprefix -if zero~=nil then -self.onboardnum0=zero -end -return self -end -function RAT:_InitAircraft(DCSgroup) -self:F2(DCSgroup) -local DCSunit=DCSgroup:getUnit(1) -local DCSdesc=DCSunit:getDesc() -local DCScategory=DCSgroup:getCategory() -local DCStype=DCSunit:getTypeName() -if DCScategory==Group.Category.AIRPLANE then -self.category=RAT.cat.plane -elseif DCScategory==Group.Category.HELICOPTER then -self.category=RAT.cat.heli -else -self.category="other" -self:E(RAT.id.."ERROR: Group of RAT is neither airplane nor helicopter!") -end -self.aircraft.type=DCStype -self.aircraft.fuel=DCSunit:getFuel() -self.aircraft.Rmax=DCSdesc.range*RAT.unit.nm2m -self.aircraft.Reff=self.aircraft.Rmax*self.aircraft.fuel*0.95 -self.aircraft.Vmax=DCSdesc.speedMax -self.aircraft.Vymax=DCSdesc.VyMax -self.aircraft.ceiling=DCSdesc.Hmax -if DCSdesc.box then -self.aircraft.length=DCSdesc.box.max.x -self.aircraft.height=DCSdesc.box.max.y -self.aircraft.width=DCSdesc.box.max.z -elseif DCStype=="Mirage-F1CE"then -self.aircraft.length=16 -self.aircraft.height=5 -self.aircraft.width=9 -end -self.aircraft.box=math.max(self.aircraft.length,self.aircraft.width) -local text=string.format("\n******************************************************\n") -text=text..string.format("Aircraft parameters:\n") -text=text..string.format("Template group = %s\n",self.SpawnTemplatePrefix) -text=text..string.format("Alias = %s\n",self.alias) -text=text..string.format("Category = %s\n",self.category) -text=text..string.format("Type = %s\n",self.aircraft.type) -text=text..string.format("Length (x) = %6.1f m\n",self.aircraft.length) -text=text..string.format("Width (z) = %6.1f m\n",self.aircraft.width) -text=text..string.format("Height (y) = %6.1f m\n",self.aircraft.height) -text=text..string.format("Max air speed = %6.1f m/s\n",self.aircraft.Vmax) -text=text..string.format("Max climb speed = %6.1f m/s\n",self.aircraft.Vymax) -text=text..string.format("Initial Fuel = %6.1f\n",self.aircraft.fuel*100) -text=text..string.format("Max range = %6.1f km\n",self.aircraft.Rmax/1000) -text=text..string.format("Eff range = %6.1f km (with 95 percent initial fuel amount)\n",self.aircraft.Reff/1000) -text=text..string.format("Ceiling = %6.1f km = FL%3.0f\n",self.aircraft.ceiling/1000,self.aircraft.ceiling/RAT.unit.FL2m) -text=text..string.format("******************************************************\n") -self:T(RAT.id..text) -end -function RAT:_SpawnWithRoute(_departure,_destination,_takeoff,_landing,_livery,_waypoint,_lastpos,_nrespawn,parkingdata) -self:F({rat=RAT.id,departure=_departure,destination=_destination,takeoff=_takeoff,landing=_landing,livery=_livery,waypoint=_waypoint,lastpos=_lastpos,nrespawn=_nrespawn}) -local takeoff=self.takeoff -local landing=self.landing -if _takeoff then -takeoff=_takeoff -end -if _landing then -landing=_landing -end -if takeoff==RAT.wp.coldorhot then -local temp={RAT.wp.cold,RAT.wp.hot} -takeoff=temp[math.random(2)] -end -local nrespawn=0 -if _nrespawn then -nrespawn=_nrespawn -end -local departure,destination,waypoints,WPholding,WPfinal=self:_SetRoute(takeoff,landing,_departure,_destination,_waypoint) -if not(departure and destination and waypoints)then -return nil -end -local livery -if _livery then -livery=_livery -elseif self.livery then -livery=self.livery[math.random(#self.livery)] -local text=string.format("Chosen livery for group %s: %s",self:_AnticipatedGroupName(),livery) -self:T(RAT.id..text) -else -livery=nil -end -local successful=self:_ModifySpawnTemplate(waypoints,livery,_lastpos,departure,takeoff,parkingdata) -if not successful then -return nil -end -local group=self:SpawnWithIndex(self.SpawnIndex) -self.alive=self.alive+1 -self:T(RAT.id..string.format("Alive groups counter now = %d.",self.alive)) -if self.ATCswitch and landing==RAT.wp.landing then -if self.returnzone then -self:_ATCAddFlight(group:GetName(),departure:GetName()) -else -self:_ATCAddFlight(group:GetName(),destination:GetName()) -end -end -if self.placemarkers then -self:_PlaceMarkers(waypoints,self.SpawnIndex) -end -if self.invisible then -self:_CommandInvisible(group,true) -end -if self.immortal then -self:_CommandImmortal(group,true) -end -if self.eplrs then -group:CommandEPLRS(true,1) -end -self:_SetROE(group,self.roe) -self:_SetROT(group,self.rot) -self.ratcraft[self.SpawnIndex]={} -self.ratcraft[self.SpawnIndex]["group"]=group -self.ratcraft[self.SpawnIndex]["destination"]=destination -self.ratcraft[self.SpawnIndex]["departure"]=departure -self.ratcraft[self.SpawnIndex]["waypoints"]=waypoints -self.ratcraft[self.SpawnIndex]["airborne"]=group:InAir() -self.ratcraft[self.SpawnIndex]["nunits"]=group:GetInitialSize() -if group:InAir()then -self.ratcraft[self.SpawnIndex]["Tground"]=nil -self.ratcraft[self.SpawnIndex]["Pground"]=nil -self.ratcraft[self.SpawnIndex]["Uground"]=nil -self.ratcraft[self.SpawnIndex]["Tlastcheck"]=nil -else -self.ratcraft[self.SpawnIndex]["Tground"]=timer.getTime() -self.ratcraft[self.SpawnIndex]["Pground"]=group:GetCoordinate() -self.ratcraft[self.SpawnIndex]["Uground"]={} -for _,_unit in pairs(group:GetUnits())do -local _unitname=_unit:GetName() -self.ratcraft[self.SpawnIndex]["Uground"][_unitname]=_unit:GetCoordinate() -end -self.ratcraft[self.SpawnIndex]["Tlastcheck"]=timer.getTime() -end -self.ratcraft[self.SpawnIndex]["P0"]=group:GetCoordinate() -self.ratcraft[self.SpawnIndex]["Pnow"]=group:GetCoordinate() -self.ratcraft[self.SpawnIndex]["Distance"]=0 -self.ratcraft[self.SpawnIndex].takeoff=takeoff -self.ratcraft[self.SpawnIndex].landing=landing -self.ratcraft[self.SpawnIndex].wpholding=WPholding -self.ratcraft[self.SpawnIndex].wpfinal=WPfinal -self.ratcraft[self.SpawnIndex].active=not self.uncontrolled -self.ratcraft[self.SpawnIndex]["status"]=RAT.status.Spawned -self.ratcraft[self.SpawnIndex].livery=livery -self.ratcraft[self.SpawnIndex].despawnme=false -self.ratcraft[self.SpawnIndex].nrespawn=nrespawn -if self.f10menu then -local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex) -self.Menu[self.SubMenuName].groups[self.SpawnIndex]=MENU_MISSION:New(name,self.Menu[self.SubMenuName].groups) -self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"]=MENU_MISSION:New("Set ROE",self.Menu[self.SubMenuName].groups[self.SpawnIndex]) -MENU_MISSION_COMMAND:New("Weapons hold",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,group,RAT.ROE.weaponhold) -MENU_MISSION_COMMAND:New("Weapons free",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,group,RAT.ROE.weaponfree) -MENU_MISSION_COMMAND:New("Return fire",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,group,RAT.ROE.returnfire) -self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"]=MENU_MISSION:New("Set ROT",self.Menu[self.SubMenuName].groups[self.SpawnIndex]) -MENU_MISSION_COMMAND:New("No reaction",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,group,RAT.ROT.noreaction) -MENU_MISSION_COMMAND:New("Passive defense",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,group,RAT.ROT.passive) -MENU_MISSION_COMMAND:New("Evade on fire",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,group,RAT.ROT.evade) -MENU_MISSION_COMMAND:New("Despawn group",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self._Despawn,self,group) -MENU_MISSION_COMMAND:New("Place markers",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self._PlaceMarkers,self,waypoints,self.SpawnIndex) -MENU_MISSION_COMMAND:New("Status report",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self.Status,self,true,self.SpawnIndex) -end -return self.SpawnIndex -end -function RAT:ClearForLanding(name) -trigger.action.setUserFlag(name,1) -local flagvalue=trigger.misc.getUserFlag(name) -self:T(RAT.id.."ATC: User flag value (landing) for "..name.." set to "..flagvalue) -end -function RAT:_Respawn(index,lastpos,delay) -local departure=self.ratcraft[index].departure -local destination=self.ratcraft[index].destination -local takeoff=self.ratcraft[index].takeoff -local landing=self.ratcraft[index].landing -local livery=self.ratcraft[index].livery -local lastwp=self.ratcraft[index].waypoints[#self.ratcraft[index].waypoints] -local _departure=nil -local _destination=nil -local _takeoff=nil -local _landing=nil -local _livery=nil -local _lastwp=nil -local _lastpos=nil -if self.continuejourney then -_departure=destination:GetName() -_livery=livery -if landing==RAT.wp.landing and lastpos and not(self.respawn_at_landing or self.respawn_after_takeoff)then -if destination:GetCategory()==4 then -_lastpos=lastpos -end -end -if self.destinationzone then -_takeoff=RAT.wp.air -_landing=RAT.wp.air -elseif self.returnzone then -_takeoff=self.takeoff -if self.takeoff==RAT.wp.air then -_landing=RAT.wp.air -else -_landing=RAT.wp.landing -end -_departure=departure:GetName() -else -_takeoff=self.takeoff -_landing=self.landing -end -elseif self.commute then -if self.starshape==true then -if destination:GetName()==self.homebase then -_departure=self.homebase -_destination=nil -else -_departure=destination:GetName() -_destination=self.homebase -end -else -_departure=destination:GetName() -_destination=departure:GetName() -end -_livery=livery -if landing==RAT.wp.landing and lastpos and not(self.respawn_at_landing or self.respawn_after_takeoff)then -if destination:GetCategory()==4 then -_lastpos=lastpos -end -end -if self.destinationzone then -if self.takeoff==RAT.wp.air then -_takeoff=RAT.wp.air -_landing=RAT.wp.air -else -if takeoff==RAT.wp.air then -_takeoff=self.takeoff -_landing=RAT.wp.air -else -_takeoff=RAT.wp.air -_landing=RAT.wp.landing -end -end -elseif self.returnzone then -_departure=departure:GetName() -_destination=destination:GetName() -_takeoff=self.takeoff -_landing=self.landing -end -end -if _takeoff==RAT.wp.air and(self.continuejourney or self.commute)then -_lastwp=lastwp -end -self:T2({departure=_departure,destination=_destination,takeoff=_takeoff,landing=_landing,livery=_livery,lastwp=_lastwp}) -local respawndelay -if delay then -respawndelay=delay -elseif self.respawn_delay then -respawndelay=self.respawn_delay+3 -else -respawndelay=3 -end -local arg={} -arg.self=self -arg.departure=_departure -arg.destination=_destination -arg.takeoff=_takeoff -arg.landing=_landing -arg.livery=_livery -arg.lastwp=_lastwp -arg.lastpos=_lastpos -self:T(RAT.id..string.format("%s delayed respawn in %.1f seconds.",self.alias,respawndelay)) -SCHEDULER:New(nil,self._SpawnWithRouteTimer,{arg},respawndelay) -end -function RAT._SpawnWithRouteTimer(arg) -RAT._SpawnWithRoute(arg.self,arg.departure,arg.destination,arg.takeoff,arg.landing,arg.livery,arg.lastwp,arg.lastpos) -end -function RAT:_SetRoute(takeoff,landing,_departure,_destination,_waypoint) -local VxCruiseMax -if self.Vcruisemax then -VxCruiseMax=math.min(self.Vcruisemax,self.aircraft.Vmax) -else -VxCruiseMax=math.min(self.aircraft.Vmax*0.90,250) -end -local VxCruiseMin=math.min(VxCruiseMax*0.70,166) -local VxCruise=UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin,(VxCruiseMax-VxCruiseMax)/4,VxCruiseMin,VxCruiseMax) -local VxClimb=math.min(self.aircraft.Vmax*0.90,200) -local VxDescent=math.min(self.aircraft.Vmax*0.60,140) -local VxHolding=VxDescent*0.9 -local VxFinal=VxHolding*0.9 -local VyClimb=math.min(self.Vclimb*RAT.unit.ft2meter/60,self.aircraft.Vymax) -local AlphaClimb=math.asin(VyClimb/VxClimb) -local AlphaDescent=math.rad(self.AlphaDescent) -local FLcruise_expect=self.FLcruise -local departure=nil -if _departure then -if self:_AirportExists(_departure)then -departure=AIRBASE:FindByName(_departure) -if takeoff==RAT.wp.air then -departure=departure:GetZone() -end -elseif self:_ZoneExists(_departure)then -departure=ZONE:FindByName(_departure) -else -local text=string.format("ERROR! Specified departure airport %s does not exist for %s.",_departure,self.alias) -self:E(RAT.id..text) -end -else -departure=self:_PickDeparture(takeoff) -if self.commute and self.starshape==true and self.homebase==nil then -self.homebase=departure:GetName() -end -end -if not departure then -local text=string.format("ERROR! No valid departure airport could be found for %s.",self.alias) -self:E(RAT.id..text) -return nil -end -local Pdeparture -if takeoff==RAT.wp.air then -if _waypoint then -Pdeparture=COORDINATE:New(_waypoint.x,_waypoint.alt,_waypoint.y) -else -local vec2=departure:GetRandomVec2() -Pdeparture=COORDINATE:NewFromVec2(vec2) -end -else -Pdeparture=departure:GetCoordinate() -end -local H_departure -if takeoff==RAT.wp.air then -local Hmin -if self.category==RAT.cat.plane then -Hmin=1000 -else -Hmin=50 -end -H_departure=self:_Randomize(FLcruise_expect*0.7,0.3,Pdeparture.y+Hmin,FLcruise_expect) -if self.FLminuser then -H_departure=math.max(H_departure,self.FLminuser) -end -if _waypoint then -H_departure=_waypoint.alt -end -else -H_departure=Pdeparture.y -end -local mindist=self.mindist -if self.FLminuser then -local hclimb=self.FLminuser-H_departure -local hdescent=self.FLminuser-H_departure -local Dclimb,Ddescent,Dtot=self:_MinDistance(AlphaClimb,AlphaDescent,hclimb,hdescent) -if takeoff==RAT.wp.air and landing==RAT.wpair then -mindist=0 -elseif takeoff==RAT.wp.air then -mindist=Ddescent -elseif landing==RAT.wp.air then -mindist=Dclimb -else -mindist=Dtot -end -mindist=math.max(self.mindist,mindist) -local text=string.format("Adjusting min distance to %d km (for given min FL%03d)",mindist/1000,self.FLminuser/RAT.unit.FL2m) -self:T(RAT.id..text) -end -local destination=nil -if _destination then -if self:_AirportExists(_destination)then -destination=AIRBASE:FindByName(_destination) -if landing==RAT.wp.air or self.returnzone then -destination=destination:GetZone() -end -elseif self:_ZoneExists(_destination)then -destination=ZONE:FindByName(_destination) -else -local text=string.format("ERROR: Specified destination airport/zone %s does not exist for %s!",_destination,self.alias) -self:E(RAT.id.."ERROR: "..text) -end -else -local random=self.random_destination -if self.continuejourney and _departure and#self.destination_ports<3 then -random=true -end -local mylanding=landing -local acrange=self.aircraft.Reff -if self.returnzone then -mylanding=RAT.wp.air -acrange=self.aircraft.Reff/2 -end -destination=self:_PickDestination(departure,Pdeparture,mindist,math.min(acrange,self.maxdist),random,mylanding) -end -if not destination then -local text=string.format("No valid destination airport could be found for %s!",self.alias) -MESSAGE:New(text,60):ToAll() -self:E(RAT.id.."ERROR: "..text) -return nil -end -if destination:GetName()==departure:GetName()then -local text=string.format("%s: Destination and departure are identical. Airport/zone %s.",self.alias,destination:GetName()) -MESSAGE:New(text,30):ToAll() -self:E(RAT.id.."ERROR: "..text) -end -local Preturn -local destination_returnzone -if self.returnzone then -local vec2=destination:GetRandomVec2() -Preturn=COORDINATE:NewFromVec2(vec2) -destination_returnzone=destination -destination=departure -end -local Pdestination -if landing==RAT.wp.air then -local vec2=destination:GetRandomVec2() -Pdestination=COORDINATE:NewFromVec2(vec2) -else -Pdestination=destination:GetCoordinate() -end -local H_destination=Pdestination.y -local Rhmin=8000 -local Rhmax=20000 -if self.category==RAT.cat.heli then -Rhmin=500 -Rhmax=1000 -end -local Vholding=Pdestination:GetRandomVec2InRadius(Rhmax,Rhmin) -local Pholding=COORDINATE:NewFromVec2(Vholding) -local H_holding=Pholding.y -local h_holding -if self.category==RAT.cat.plane then -h_holding=1200 -else -h_holding=150 -end -h_holding=self:_Randomize(h_holding,0.2) -local Hh_holding=H_holding+h_holding -if landing==RAT.wp.air then -Hh_holding=H_departure -end -local d_holding=Pholding:Get2DDistance(Pdestination) -local heading -local d_total -if self.returnzone then -heading=self:_Course(Pdeparture,Preturn) -d_total=Pdeparture:Get2DDistance(Preturn)+Preturn:Get2DDistance(Pholding) -else -heading=self:_Course(Pdeparture,Pholding) -d_total=Pdeparture:Get2DDistance(Pholding) -end -if takeoff==RAT.wp.air then -local H_departure_max -if landing==RAT.wp.air then -H_departure_max=H_departure -else -H_departure_max=d_total*math.tan(AlphaDescent)+Hh_holding -end -H_departure=math.min(H_departure,H_departure_max) -end -local deltaH=math.abs(H_departure-Hh_holding) -local phi=math.atan(deltaH/d_total) -local phi_climb -local phi_descent -if(H_departure>Hh_holding)then -phi_climb=AlphaClimb+phi -phi_descent=AlphaDescent-phi -else -phi_climb=AlphaClimb-phi -phi_descent=AlphaDescent+phi -end -local D_total -if self.returnzone then -D_total=math.sqrt(deltaH*deltaH+d_total/2*d_total/2) -else -D_total=math.sqrt(deltaH*deltaH+d_total*d_total) -end -local gamma=math.rad(180)-phi_climb-phi_descent -local a=D_total*math.sin(phi_climb)/math.sin(gamma) -local b=D_total*math.sin(phi_descent)/math.sin(gamma) -local hphi_max=b*math.sin(phi_climb) -local hphi_max2=a*math.sin(phi_descent) -local h_max1=b*math.sin(AlphaClimb) -local h_max2=a*math.sin(AlphaDescent) -local h_max -if(H_departure>Hh_holding)then -h_max=math.min(h_max1,h_max2) -else -h_max=math.max(h_max1,h_max2) -end -local FLmax=h_max+H_departure -local FLmin=math.max(H_departure,Hh_holding) -if self.category==RAT.cat.heli then -FLmin=math.max(H_departure,H_destination)+50 -FLmax=math.max(H_departure,H_destination)+1000 -end -FLmax=math.min(FLmax,self.aircraft.ceiling) -if self.FLminuser then -FLmin=math.max(self.FLminuser,FLmin) -end -if self.FLmaxuser then -FLmax=math.min(self.FLmaxuser,FLmax) -end -if FLmin>FLmax then -FLmin=FLmax -end -if FLcruise_expectFLmax then -FLcruise_expect=FLmax -end -local FLcruise=UTILS.RandomGaussian(FLcruise_expect,math.abs(FLmax-FLmin)/4,FLmin,FLmax) -if self.FLuser then -FLcruise=self.FLuser -FLcruise=math.max(FLcruise,FLmin) -FLcruise=math.min(FLcruise,FLmax) -end -local h_climb=FLcruise-H_departure -local h_descent=FLcruise-Hh_holding -local d_climb=h_climb/math.tan(AlphaClimb) -local d_descent=h_descent/math.tan(AlphaDescent) -local d_cruise=d_total-d_climb-d_descent -local text=string.format("\n******************************************************\n") -text=text..string.format("Template = %s\n",self.SpawnTemplatePrefix) -text=text..string.format("Alias = %s\n",self.alias) -text=text..string.format("Group name = %s\n\n",self:_AnticipatedGroupName()) -text=text..string.format("Speeds:\n") -text=text..string.format("VxCruiseMin = %6.1f m/s = %5.1f km/h\n",VxCruiseMin,VxCruiseMin*3.6) -text=text..string.format("VxCruiseMax = %6.1f m/s = %5.1f km/h\n",VxCruiseMax,VxCruiseMax*3.6) -text=text..string.format("VxCruise = %6.1f m/s = %5.1f km/h\n",VxCruise,VxCruise*3.6) -text=text..string.format("VxClimb = %6.1f m/s = %5.1f km/h\n",VxClimb,VxClimb*3.6) -text=text..string.format("VxDescent = %6.1f m/s = %5.1f km/h\n",VxDescent,VxDescent*3.6) -text=text..string.format("VxHolding = %6.1f m/s = %5.1f km/h\n",VxHolding,VxHolding*3.6) -text=text..string.format("VxFinal = %6.1f m/s = %5.1f km/h\n",VxFinal,VxFinal*3.6) -text=text..string.format("VyClimb = %6.1f m/s\n",VyClimb) -text=text..string.format("\nDistances:\n") -text=text..string.format("d_climb = %6.1f km\n",d_climb/1000) -text=text..string.format("d_cruise = %6.1f km\n",d_cruise/1000) -text=text..string.format("d_descent = %6.1f km\n",d_descent/1000) -text=text..string.format("d_holding = %6.1f km\n",d_holding/1000) -text=text..string.format("d_total = %6.1f km\n",d_total/1000) -text=text..string.format("\nHeights:\n") -text=text..string.format("H_departure = %6.1f m ASL\n",H_departure) -text=text..string.format("H_destination = %6.1f m ASL\n",H_destination) -text=text..string.format("H_holding = %6.1f m ASL\n",H_holding) -text=text..string.format("h_climb = %6.1f m\n",h_climb) -text=text..string.format("h_descent = %6.1f m\n",h_descent) -text=text..string.format("h_holding = %6.1f m\n",h_holding) -text=text..string.format("delta H = %6.1f m\n",deltaH) -text=text..string.format("FLmin = %6.1f m ASL = FL%03d\n",FLmin,FLmin/RAT.unit.FL2m) -text=text..string.format("FLcruise = %6.1f m ASL = FL%03d\n",FLcruise,FLcruise/RAT.unit.FL2m) -text=text..string.format("FLmax = %6.1f m ASL = FL%03d\n",FLmax,FLmax/RAT.unit.FL2m) -text=text..string.format("\nAngles:\n") -text=text..string.format("Alpha climb = %6.2f Deg\n",math.deg(AlphaClimb)) -text=text..string.format("Alpha descent = %6.2f Deg\n",math.deg(AlphaDescent)) -text=text..string.format("Phi (slope) = %6.2f Deg\n",math.deg(phi)) -text=text..string.format("Phi climb = %6.2f Deg\n",math.deg(phi_climb)) -text=text..string.format("Phi descent = %6.2f Deg\n",math.deg(phi_descent)) -if self.Debug then -local h_climb_max=FLmax-H_departure -local h_descent_max=FLmax-Hh_holding -local d_climb_max=h_climb_max/math.tan(AlphaClimb) -local d_descent_max=h_descent_max/math.tan(AlphaDescent) -local d_cruise_max=d_total-d_climb_max-d_descent_max -text=text..string.format("Heading = %6.1f Deg\n",heading) -text=text..string.format("\nSSA triangle:\n") -text=text..string.format("D_total = %6.1f km\n",D_total/1000) -text=text..string.format("gamma = %6.1f Deg\n",math.deg(gamma)) -text=text..string.format("a = %6.1f m\n",a) -text=text..string.format("b = %6.1f m\n",b) -text=text..string.format("hphi_max = %6.1f m\n",hphi_max) -text=text..string.format("hphi_max2 = %6.1f m\n",hphi_max2) -text=text..string.format("h_max1 = %6.1f m\n",h_max1) -text=text..string.format("h_max2 = %6.1f m\n",h_max2) -text=text..string.format("h_max = %6.1f m\n",h_max) -text=text..string.format("\nMax heights and distances:\n") -text=text..string.format("d_climb_max = %6.1f km\n",d_climb_max/1000) -text=text..string.format("d_cruise_max = %6.1f km\n",d_cruise_max/1000) -text=text..string.format("d_descent_max = %6.1f km\n",d_descent_max/1000) -text=text..string.format("h_climb_max = %6.1f m\n",h_climb_max) -text=text..string.format("h_descent_max = %6.1f m\n",h_descent_max) -end -text=text..string.format("******************************************************\n") -self:T2(RAT.id..text) -if d_cruise<0 then -d_cruise=100 -end -local wp={} -local c={} -local wpholding=nil -local wpfinal=nil -c[#c+1]=Pdeparture -wp[#wp+1]=self:_Waypoint(#wp+1,"Departure",takeoff,c[#wp+1],VxClimb,H_departure,departure) -self.waypointdescriptions[#wp]="Departure" -self.waypointstatus[#wp]=RAT.status.Departure -if takeoff==RAT.wp.air then -if d_climb<5000 or d_cruise<5000 then -d_cruise=d_cruise+d_climb -else -c[#c+1]=c[#c]:Translate(d_climb,heading) -wp[#wp+1]=self:_Waypoint(#wp+1,"Begin of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) -self.waypointdescriptions[#wp]="Begin of Cruise" -self.waypointstatus[#wp]=RAT.status.Cruise -end -else -c[#c+1]=c[#c]:Translate(d_climb/2,heading) -c[#c+1]=c[#c]:Translate(d_climb/2,heading) -wp[#wp+1]=self:_Waypoint(#wp+1,"Climb",RAT.wp.climb,c[#wp+1],VxClimb,H_departure+(FLcruise-H_departure)/2) -self.waypointdescriptions[#wp]="Climb" -self.waypointstatus[#wp]=RAT.status.Climb -wp[#wp+1]=self:_Waypoint(#wp+1,"Begin of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) -self.waypointdescriptions[#wp]="Begin of Cruise" -self.waypointstatus[#wp]=RAT.status.Cruise -end -if self.returnzone then -c[#c+1]=Preturn -wp[#wp+1]=self:_Waypoint(#wp+1,"Return Zone",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) -self.waypointdescriptions[#wp]="Return Zone" -self.waypointstatus[#wp]=RAT.status.Uturn -end -if landing==RAT.wp.air then -c[#c+1]=Pdestination -wp[#wp+1]=self:_Waypoint(#wp+1,"Final Destination",RAT.wp.finalwp,c[#wp+1],VxCruise,FLcruise) -self.waypointdescriptions[#wp]="Final Destination" -self.waypointstatus[#wp]=RAT.status.Destination -elseif self.returnzone then -c[#c+1]=c[#c]:Translate(d_cruise/2,heading-180) -wp[#wp+1]=self:_Waypoint(#wp+1,"End of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) -self.waypointdescriptions[#wp]="End of Cruise" -self.waypointstatus[#wp]=RAT.status.Descent -else -c[#c+1]=c[#c]:Translate(d_cruise,heading) -wp[#wp+1]=self:_Waypoint(#wp+1,"End of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) -self.waypointdescriptions[#wp]="End of Cruise" -self.waypointstatus[#wp]=RAT.status.Descent -end -if landing==RAT.wp.landing then -if self.returnzone then -c[#c+1]=c[#c]:Translate(d_descent/2,heading-180) -wp[#wp+1]=self:_Waypoint(#wp+1,"Descent",RAT.wp.descent,c[#wp+1],VxDescent,FLcruise-(FLcruise-(h_holding+H_holding))/2) -self.waypointdescriptions[#wp]="Descent" -self.waypointstatus[#wp]=RAT.status.DescentHolding -else -c[#c+1]=c[#c]:Translate(d_descent/2,heading) -wp[#wp+1]=self:_Waypoint(#wp+1,"Descent",RAT.wp.descent,c[#wp+1],VxDescent,FLcruise-(FLcruise-(h_holding+H_holding))/2) -self.waypointdescriptions[#wp]="Descent" -self.waypointstatus[#wp]=RAT.status.DescentHolding -end -end -if landing==RAT.wp.landing then -c[#c+1]=Pholding -wp[#wp+1]=self:_Waypoint(#wp+1,"Holding Point",RAT.wp.holding,c[#wp+1],VxHolding,H_holding+h_holding) -self.waypointdescriptions[#wp]="Holding Point" -self.waypointstatus[#wp]=RAT.status.Holding -wpholding=#wp -c[#c+1]=Pdestination -wp[#wp+1]=self:_Waypoint(#wp+1,"Final Destination",landing,c[#wp+1],VxFinal,H_destination,destination) -self.waypointdescriptions[#wp]="Final Destination" -self.waypointstatus[#wp]=RAT.status.Destination -end -wpfinal=#wp -local waypoints={} -for _,p in ipairs(wp)do -table.insert(waypoints,p) -end -self:_Routeinfo(waypoints,"Waypoint info in set_route:") -if self.returnzone then -return departure,destination_returnzone,waypoints,wpholding,wpfinal -else -return departure,destination,waypoints,wpholding,wpfinal -end -end -function RAT:_PickDeparture(takeoff) -local departures={} -if self.random_departure then -for _,_airport in pairs(self.airports)do -local airport=_airport -local name=airport:GetName() -if not self:_Excluded(name)then -if takeoff==RAT.wp.air then -table.insert(departures,airport:GetZone()) -else -local nspots=1 -if self.termtype~=nil then -nspots=airport:GetParkingSpotsNumber(self.termtype) -end -if nspots>0 then -table.insert(departures,airport) -end -end -end -end -else -for _,name in pairs(self.departure_ports)do -local dep=nil -if self:_AirportExists(name)then -if takeoff==RAT.wp.air then -dep=AIRBASE:FindByName(name):GetZone() -else -dep=AIRBASE:FindByName(name) -if self.termtype~=nil and dep~=nil then -local _dep=dep -local nspots=_dep:GetParkingSpotsNumber(self.termtype) -if nspots==0 then -dep=nil -end -end -end -elseif self:_ZoneExists(name)then -if takeoff==RAT.wp.air then -dep=ZONE:FindByName(name) -else -self:E(RAT.id..string.format("ERROR! Takeoff is not in air. Cannot use %s as departure.",name)) -end -else -self:E(RAT.id..string.format("ERROR: No airport or zone found with name %s.",name)) -end -if dep then -table.insert(departures,dep) -end -end -end -self:T(RAT.id..string.format("Number of possible departures for %s= %d",self.alias,#departures)) -local departure=departures[math.random(#departures)] -local text -if departure and departure:GetName()then -if takeoff==RAT.wp.air then -text=string.format("%s: Chosen departure zone: %s",self.alias,departure:GetName()) -else -text=string.format("%s: Chosen departure airport: %s (ID %d)",self.alias,departure:GetName(),departure:GetID()) -end -self:T(RAT.id..text) -else -self:E(RAT.id..string.format("ERROR! No departure airport or zone found for %s.",self.alias)) -departure=nil -end -return departure -end -function RAT:_PickDestination(departure,q,minrange,maxrange,random,landing) -minrange=minrange or self.mindist -maxrange=maxrange or self.maxdist -local destinations={} -if random then -for _,_airport in pairs(self.airports)do -local airport=_airport -local name=airport:GetName() -if self:_IsFriendly(name)and not self:_Excluded(name)and name~=departure:GetName()then -local distance=q:Get2DDistance(airport:GetCoordinate()) -if distance>=minrange and distance<=maxrange then -if landing==RAT.wp.air then -table.insert(destinations,airport:GetZone()) -else -local nspot=1 -if self.termtype then -nspot=airport:GetParkingSpotsNumber(self.termtype) -end -if nspot>0 then -table.insert(destinations,airport) -end -end -end -end -end -else -for _,name in pairs(self.destination_ports)do -if name~=departure:GetName()then -local dest=nil -if self:_AirportExists(name)then -if landing==RAT.wp.air then -dest=AIRBASE:FindByName(name):GetZone() -else -dest=AIRBASE:FindByName(name) -local nspot=1 -if self.termtype then -nspot=dest:GetParkingSpotsNumber(self.termtype) -end -if nspot==0 then -dest=nil -end -end -elseif self:_ZoneExists(name)then -if landing==RAT.wp.air then -dest=ZONE:FindByName(name) -else -self:E(RAT.id..string.format("ERROR! Landing is not in air. Cannot use zone %s as destination!",name)) -end -else -self:E(RAT.id..string.format("ERROR! No airport or zone found with name %s",name)) -end -if dest then -local distance=q:Get2DDistance(dest:GetCoordinate()) -if distance>=minrange and distance<=maxrange then -table.insert(destinations,dest) -else -local text=string.format("Destination %s is ouside range. Distance = %5.1f km, min = %5.1f km, max = %5.1f km.",name,distance,minrange,maxrange) -self:T(RAT.id..text) -end -end -end -end -end -self:T(RAT.id..string.format("Number of possible destinations = %s.",#destinations)) -if#destinations>0 then -local function compare(a,b) -local qa=q:Get2DDistance(a:GetCoordinate()) -local qb=q:Get2DDistance(b:GetCoordinate()) -return qa0 then -destination=destinations[math.random(#destinations)] -local text -if landing==RAT.wp.air then -text=string.format("%s: Chosen destination zone: %s.",self.alias,destination:GetName()) -else -text=string.format("%s Chosen destination airport: %s (ID %d).",self.alias,destination:GetName(),destination:GetID()) -end -self:T(RAT.id..text) -else -self:E(RAT.id.."ERROR! No destination airport or zone found.") -destination=nil -end -return destination -end -function RAT:_GetAirportsInZone(zone) -local airports={} -for _,airport in pairs(self.airports)do -local name=airport:GetName() -local coord=airport:GetCoordinate() -if zone:IsPointVec3InZone(coord)then -table.insert(airports,name) -end -end -return airports -end -function RAT:_Excluded(port) -for _,name in pairs(self.excluded_ports)do -if name==port then -return true -end -end -return false -end -function RAT:_IsFriendly(port) -for _,airport in pairs(self.airports)do -local name=airport:GetName() -if name==port then -return true -end -end -return false -end -function RAT:_GetAirportsOfMap() -local _coalition -for i=0,2 do -if i==0 then -_coalition=coalition.side.NEUTRAL -elseif i==1 then -_coalition=coalition.side.RED -elseif i==2 then -_coalition=coalition.side.BLUE -end -local ab=coalition.getAirbases(i) -for _,airbase in pairs(ab)do -local _id=airbase:getID() -local _p=airbase:getPosition().p -local _name=airbase:getName() -local _myab=AIRBASE:FindByName(_name) -if _myab then -table.insert(self.airports_map,_myab) -local text="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName() -self:T(RAT.id..text) -else -self:E(RAT.id..string.format("WARNING: Airbase %s does not exsist as MOOSE object!",tostring(_name))) -end -end -end -end -function RAT:_GetAirportsOfCoalition() -for _,coalition in pairs(self.ctable)do -for _,_airport in pairs(self.airports_map)do -local airport=_airport -local category=airport:GetAirbaseCategory() -if airport:GetCoalition()==coalition then -local condition1=self.category==RAT.cat.plane and category==Airbase.Category.HELIPAD -local condition2=self.category==RAT.cat.plane and category==Airbase.Category.SHIP -if not(condition1 or condition2)then -table.insert(self.airports,airport) -end -end -end -end -if#self.airports==0 then -local text=string.format("No possible departure/destination airports found for RAT %s.",tostring(self.alias)) -MESSAGE:New(text,10):ToAll() -self:E(RAT.id..text) -end -end -function RAT:Status(message,forID) -if message==nil then -message=false -end -if forID==nil then -forID=false -end -local Tnow=timer.getTime() -local nalive=0 -for spawnindex,ratcraft in ipairs(self.ratcraft)do -local group=ratcraft.group -if group and group:IsAlive()and(group:GetCoordinate()or group:GetVec3())then -nalive=nalive+1 -local prefix=self:_GetPrefixFromGroup(group) -local life=self:_GetLife(group) -local fuel=group:GetFuel()*100.0 -local airborne=group:InAir() -local coords=group:GetCoordinate()or group:GetVec3() -local alt=1000 -if coords then -alt=coords.y or 1000 -end -local departure=ratcraft.departure:GetName() -local destination=ratcraft.destination:GetName() -local type=self.aircraft.type -local status=ratcraft.status -local active=ratcraft.active -local Nunits=ratcraft.nunits -local N0units=group:GetInitialSize() -local Tg=0 -local Dg=0 -local dTlast=0 -local stationary=false -if airborne then -ratcraft["Tground"]=nil -ratcraft["Pground"]=nil -ratcraft["Uground"]=nil -ratcraft["Tlastcheck"]=nil -else -if ratcraft["Tground"]then -Tg=Tnow-ratcraft["Tground"] -Dg=coords:Get2DDistance(ratcraft["Pground"]) -dTlast=Tnow-ratcraft["Tlastcheck"] -if dTlast>self.Tinactive then -for _,_unit in pairs(group:GetUnits())do -if _unit and _unit:IsAlive()then -local unitname=_unit:GetName() -local unitcoord=_unit:GetCoordinate() -local Ug=unitcoord:Get2DDistance(ratcraft.Uground[unitname]) -self:T2(RAT.id..string.format("Unit %s travelled distance on ground %.1f m since %d seconds.",unitname,Ug,dTlast)) -if Ug<50 and active and status~=RAT.status.EventBirth then -stationary=true -end -ratcraft["Uground"][unitname]=unitcoord -end -end -ratcraft["Tlastcheck"]=Tnow -ratcraft["Pground"]=coords -end -else -ratcraft["Tground"]=Tnow -ratcraft["Tlastcheck"]=Tnow -ratcraft["Pground"]=coords -ratcraft["Uground"]={} -for _,_unit in pairs(group:GetUnits())do -local unitname=_unit:GetName() -ratcraft.Uground[unitname]=_unit:GetCoordinate() -end -end -end -local Pn=coords -local Dtravel=Pn:Get2DDistance(ratcraft["Pnow"]) -ratcraft["Pnow"]=Pn -ratcraft["Distance"]=ratcraft["Distance"]+Dtravel -local Ddestination=Pn:Get2DDistance(ratcraft.destination:GetCoordinate()) -if(forID and spawnindex==forID)or(not forID)then -local text=string.format("ID %i of flight %s",spawnindex,prefix) -if N0units>1 then -text=text..string.format(" (%d/%d)\n",Nunits,N0units) -else -text=text.."\n" -end -if self.commute then -text=text..string.format("%s commuting between %s and %s\n",type,departure,destination) -elseif self.continuejourney then -text=text..string.format("%s travelling from %s to %s (and continueing form there)\n",type,departure,destination) -else -text=text..string.format("%s travelling from %s to %s\n",type,departure,destination) -end -text=text..string.format("Status: %s",status) -if airborne then -text=text.." [airborne]\n" -else -text=text.." [on ground]\n" -end -text=text..string.format("Fuel = %3.0f %%\n",fuel) -text=text..string.format("Life = %3.0f %%\n",life) -text=text..string.format("FL%03d = %i m ASL\n",alt/RAT.unit.FL2m,alt) -text=text..string.format("Distance travelled = %6.1f km\n",ratcraft["Distance"]/1000) -text=text..string.format("Distance to destination = %6.1f km",Ddestination/1000) -if not airborne then -text=text..string.format("\nTime on ground = %6.0f seconds\n",Tg) -text=text..string.format("Position change = %8.1f m since %3.0f seconds.",Dg,dTlast) -end -self:T(RAT.id..text) -if message then -MESSAGE:New(text,20):ToAll() -end -end -if not airborne then -if stationary then -local text=string.format("Group %s is despawned after being %d seconds inaktive on ground.",self.alias,dTlast) -self:T(RAT.id..text) -self:_Despawn(group) -end -if life<10 and Dtravel<100 then -local text=string.format("Damaged group %s is despawned. Life = %3.0f",self.alias,life) -self:T(RAT.id..text) -self:_Despawn(group) -end -end -if ratcraft.despawnme then -local text=string.format("Flight %s will be despawned NOW!",self.alias) -self:T(RAT.id..text) -if(not self.norespawn)and(not self.respawn_after_takeoff)then -local idx=self:GetSpawnIndexFromGroup(group) -local coord=group:GetCoordinate() -self:_Respawn(idx,coord,0) -end -if self.despawnair then -self:_Despawn(group,0) -end -end -else -local text=string.format("Group does not exist in loop ratcraft status.") -self:T2(RAT.id..text) -end -end -local text=string.format("Alive groups of %s: %d, nalive=%d/%d",self.alias,self.alive,nalive,self.ngroups) -self:T(RAT.id..text) -MESSAGE:New(text,20):ToAllIf(message and not forID) -end -function RAT:_GetLife(group) -local life=0.0 -if group and group:IsAlive()then -local unit=group:GetUnit(1) -if unit then -life=unit:GetLife()/unit:GetLife0()*100 -else -self:T2(RAT.id.."ERROR! Unit does not exist in RAT_Getlife(). Returning zero.") -end -else -self:T2(RAT.id.."ERROR! Group does not exist in RAT_Getlife(). Returning zero.") -end -return life -end -function RAT:_SetStatus(group,status) -if group and group:IsAlive()then -local index=self:GetSpawnIndexFromGroup(group) -if self.ratcraft[index]then -self.ratcraft[index].status=status -local no1=status==RAT.status.Departure -local no2=status==RAT.status.EventBirthAir -local no3=status==RAT.status.Holding -local text=string.format("Flight %s: %s.",group:GetName(),status) -self:T(RAT.id..text) -if not(no1 or no2 or no3)then -MESSAGE:New(text,10):ToAllIf(self.reportstatus) -end -end -end -end -function RAT:GetStatus(group) -if group and group:IsAlive()then -local index=self:GetSpawnIndexFromGroup(group) -if self.ratcraft[index]then -return self.ratcraft[index].status -end -end -return"nonexistant" -end -function RAT:_OnBirth(EventData) -self:F3(EventData) -self:T3(RAT.id.."Captured event birth!") -local SpawnGroup=EventData.IniGroup -if SpawnGroup then -local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) -if EventPrefix then -if EventPrefix==self.alias then -local text="Event: Group "..SpawnGroup:GetName().." was born." -self:T(RAT.id..text) -local status="unknown in birth" -if SpawnGroup:InAir()then -status=RAT.status.EventBirthAir -elseif self.uncontrolled then -status=RAT.status.Uncontrolled -else -status=RAT.status.EventBirth -end -self:_SetStatus(SpawnGroup,status) -local i=self:GetSpawnIndexFromGroup(SpawnGroup) -local _departure=self.ratcraft[i].departure:GetName() -local _destination=self.ratcraft[i].destination:GetName() -local _nrespawn=self.ratcraft[i].nrespawn -local _takeoff=self.ratcraft[i].takeoff -local _landing=self.ratcraft[i].landing -local _livery=self.ratcraft[i].livery -local _airbase=AIRBASE:FindByName(_departure) -local onrunway=false -if _airbase then -if self.checkonrunway and _takeoff~=RAT.wp.runway and _takeoff~=RAT.wp.air then -onrunway=_airbase:CheckOnRunWay(SpawnGroup,self.onrunwayradius,false) -end -end -if onrunway then -local text=string.format("ERROR: RAT group of %s was spawned on runway. Group #%d will be despawned immediately!",self.alias,i) -MESSAGE:New(text,30):ToAllIf(self.Debug) -self:E(RAT.id..text) -if self.Debug then -SpawnGroup:FlareRed() -end -self:_Despawn(SpawnGroup) -if(self.Ndeparture_Airports>=2 or self.random_departure)and _nrespawn new state %s.",SpawnGroup:GetName(),currentstate,status) -self:T(RAT.id..text) -local idx=self:GetSpawnIndexFromGroup(SpawnGroup) -local coord=SpawnGroup:GetCoordinate() -self:_Respawn(idx,coord) -end -text="Event: Group "..SpawnGroup:GetName().." will be destroyed now." -self:T(RAT.id..text) -self:_Despawn(SpawnGroup) -end -end -end -else -self:T2(RAT.id.."ERROR: Group does not exist in RAT:_OnEngineShutdown().") -end -end -function RAT:_OnHit(EventData) -self:F3(EventData) -self:T(RAT.id..string.format("Captured event Hit by %s! Initiator %s. Target %s",self.alias,tostring(EventData.IniUnitName),tostring(EventData.TgtUnitName))) -local SpawnGroup=EventData.TgtGroup -if SpawnGroup then -local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) -if EventPrefix and EventPrefix==self.alias then -self:T(RAT.id..string.format("Event: Group %s was hit. Unit %s.",SpawnGroup:GetName(),tostring(EventData.TgtUnitName))) -local text=string.format("%s, unit %s was hit!",self.alias,EventData.TgtUnitName) -MESSAGE:New(text,10):ToAllIf(self.reportstatus or self.Debug) -end -end -end -function RAT:_OnDeadOrCrash(EventData) -self:F3(EventData) -self:T3(RAT.id.."Captured event DeadOrCrash!") -local SpawnGroup=EventData.IniGroup -if SpawnGroup then -local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) -if EventPrefix then -if EventPrefix==self.alias then -self.alive=self.alive-1 -local text=string.format("Event: Group %s crashed or died. Alive counter = %d.",SpawnGroup:GetName(),self.alive) -self:T(RAT.id..text) -if EventData.id==world.event.S_EVENT_CRASH then -self:_OnCrash(EventData) -elseif EventData.id==world.event.S_EVENT_DEAD then -self:_OnDead(EventData) -end -end -end -end -end -function RAT:_OnDead(EventData) -self:F3(EventData) -self:T3(RAT.id.."Captured event Dead!") -local SpawnGroup=EventData.IniGroup -if SpawnGroup then -local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) -if EventPrefix then -if EventPrefix==self.alias then -local text=string.format("Event: Group %s died. Unit %s.",SpawnGroup:GetName(),EventData.IniUnitName) -self:T(RAT.id..text) -local status=RAT.status.EventDead -self:_SetStatus(SpawnGroup,status) -end -end -else -self:T2(RAT.id.."ERROR: Group does not exist in RAT:_OnDead().") -end -end -function RAT:_OnCrash(EventData) -self:F3(EventData) -self:T3(RAT.id.."Captured event Crash!") -local SpawnGroup=EventData.IniGroup -if SpawnGroup then -local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) -if EventPrefix and EventPrefix==self.alias then -local _i=self:GetSpawnIndexFromGroup(SpawnGroup) -self.ratcraft[_i].nunits=self.ratcraft[_i].nunits-1 -local _n=self.ratcraft[_i].nunits -local _n0=SpawnGroup:GetInitialSize() -local text=string.format("Event: Group %s crashed. Unit %s. Units still alive %d of %d.",SpawnGroup:GetName(),EventData.IniUnitName,_n,_n0) -self:T(RAT.id..text) -local status=RAT.status.EventCrash -self:_SetStatus(SpawnGroup,status) -if _n==0 and self.respawn_after_crash and not self.norespawn then -local text=string.format("No units left of group %s. Group will be respawned now.",SpawnGroup:GetName()) -self:T(RAT.id..text) -local idx=self:GetSpawnIndexFromGroup(SpawnGroup) -local coord=SpawnGroup:GetCoordinate() -self:_Respawn(idx,coord) -end -end -else -if self.Debug then -self:E(RAT.id.."ERROR: Group does not exist in RAT:_OnCrash().") -end -end -end -function RAT:_Despawn(group,delay) -if group~=nil then -local index=self:GetSpawnIndexFromGroup(group) -if index~=nil then -self.ratcraft[index].group=nil -self.ratcraft[index]["status"]="Dead" -local despawndelay=0 -if delay then -despawndelay=delay -elseif self.respawn_delay then -despawndelay=self.respawn_delay -end -self:T(RAT.id..string.format("%s delayed despawn in %.1f seconds.",self.alias,despawndelay)) -SCHEDULER:New(nil,self._Destroy,{self,group},despawndelay) -if self.f10menu and self.SubMenuName~=nil then -self.Menu[self.SubMenuName]["groups"][index]:Remove() -end -end -end -end -function RAT:_Destroy(group) -self:F2(group) -local DCSGroup=group:GetDCSObject() -if DCSGroup and DCSGroup:isExist()then -local triggerdead=true -for _,DCSUnit in pairs(DCSGroup:getUnits())do -if DCSUnit then -if triggerdead then -self:_CreateEventDead(timer.getTime(),DCSUnit) -triggerdead=false -end -_DATABASE:DeleteUnit(DCSUnit:getName()) -end -end -DCSGroup:destroy() -DCSGroup=nil -end -return nil -end -function RAT:_CreateEventDead(EventTime,Initiator) -self:F({EventTime,Initiator}) -local Event={ -id=world.event.S_EVENT_DEAD, -time=EventTime, -initiator=Initiator, -} -world.onEvent(Event) -end -function RAT:_Waypoint(index,description,Type,Coord,Speed,Altitude,Airport) -local _Altitude=Altitude or Coord.y -local Hland=Coord:GetLandHeight() -local _Type=nil -local _Action=nil -local _alttype="RADIO" -if Type==RAT.wp.cold then -_Type="TakeOffParking" -_Action="From Parking Area" -_Altitude=10 -_alttype="RADIO" -elseif Type==RAT.wp.hot then -_Type="TakeOffParkingHot" -_Action="From Parking Area Hot" -_Altitude=10 -_alttype="RADIO" -elseif Type==RAT.wp.runway then -_Type="TakeOff" -_Action="From Parking Area" -_Altitude=10 -_alttype="RADIO" -elseif Type==RAT.wp.air then -_Type="Turning Point" -_Action="Turning Point" -_alttype="BARO" -elseif Type==RAT.wp.climb then -_Type="Turning Point" -_Action="Turning Point" -_alttype="BARO" -elseif Type==RAT.wp.cruise then -_Type="Turning Point" -_Action="Turning Point" -_alttype="BARO" -elseif Type==RAT.wp.descent then -_Type="Turning Point" -_Action="Turning Point" -_alttype="BARO" -elseif Type==RAT.wp.holding then -_Type="Turning Point" -_Action="Turning Point" -_alttype="BARO" -elseif Type==RAT.wp.landing then -_Type="Land" -_Action="Landing" -_Altitude=10 -_alttype="RADIO" -elseif Type==RAT.wp.finalwp then -_Type="Turning Point" -_Action="Turning Point" -_alttype="BARO" -else -self:E(RAT.id.."ERROR: Unknown waypoint type in RAT:Waypoint() function!") -_Type="Turning Point" -_Action="Turning Point" -_alttype="RADIO" -end -local text=string.format("\n******************************************************\n") -text=text..string.format("Waypoint = %d\n",index) -text=text..string.format("Template = %s\n",self.SpawnTemplatePrefix) -text=text..string.format("Alias = %s\n",self.alias) -text=text..string.format("Type: %i - %s\n",Type,_Type) -text=text..string.format("Action: %s\n",_Action) -text=text..string.format("Coord: x = %6.1f km, y = %6.1f km, alt = %6.1f m\n",Coord.x/1000,Coord.z/1000,Coord.y) -text=text..string.format("Speed = %6.1f m/s = %6.1f km/h = %6.1f knots\n",Speed,Speed*3.6,Speed*1.94384) -text=text..string.format("Land = %6.1f m ASL\n",Hland) -text=text..string.format("Altitude = %6.1f m (%s)\n",_Altitude,_alttype) -if Airport then -if Type==RAT.wp.air then -text=text..string.format("Zone = %s\n",Airport:GetName()) -else -text=text..string.format("Airport = %s\n",Airport:GetName()) -end -else -text=text..string.format("No airport/zone specified\n") -end -text=text.."******************************************************\n" -self:T2(RAT.id..text) -local RoutePoint={} -RoutePoint.x=Coord.x -RoutePoint.y=Coord.z -RoutePoint.alt=_Altitude -RoutePoint.alt_type=_alttype -RoutePoint.type=_Type -RoutePoint.action=_Action -RoutePoint.speed=Speed -RoutePoint.speed_locked=true -RoutePoint.ETA=nil -RoutePoint.ETA_locked=false -RoutePoint.name=description -if(Airport~=nil)and(Type~=RAT.wp.air)then -local AirbaseID=Airport:GetID() -local AirbaseCategory=Airport:GetAirbaseCategory() -if AirbaseCategory==Airbase.Category.SHIP then -RoutePoint.linkUnit=AirbaseID -RoutePoint.helipadId=AirbaseID -elseif AirbaseCategory==Airbase.Category.HELIPAD then -RoutePoint.linkUnit=AirbaseID -RoutePoint.helipadId=AirbaseID -elseif AirbaseCategory==Airbase.Category.AIRDROME then -RoutePoint.airdromeId=AirbaseID -else -self:T(RAT.id.."Unknown Airport category in _Waypoint()!") -end -end -RoutePoint.properties={ -["vnav"]=1, -["scale"]=0, -["angle"]=0, -["vangle"]=0, -["steer"]=2, -} -local TaskCombo={} -local TaskHolding=self:_TaskHolding({x=Coord.x,y=Coord.z},Altitude,Speed,self:_Randomize(90,0.9)) -local TaskWaypoint=self:_TaskFunction("RAT._WaypointFunction",self,index) -RoutePoint.task={} -RoutePoint.task.id="ComboTask" -RoutePoint.task.params={} -TaskCombo[#TaskCombo+1]=TaskWaypoint -if Type==RAT.wp.holding then -TaskCombo[#TaskCombo+1]=TaskHolding -end -RoutePoint.task.params.tasks=TaskCombo -return RoutePoint -end -function RAT:_Routeinfo(waypoints,comment) -local text=string.format("\n******************************************************\n") -text=text..string.format("Template = %s\n",self.SpawnTemplatePrefix) -if comment then -text=text..comment.."\n" -end -text=text..string.format("Number of waypoints = %i\n",#waypoints) -for i=1,#waypoints do -local p=waypoints[i] -text=text..string.format("WP #%i: x = %6.1f km, y = %6.1f km, alt = %6.1f m %s\n",i-1,p.x/1000,p.y/1000,p.alt,self.waypointdescriptions[i]) -end -local total=0.0 -for i=1,#waypoints-1 do -local point1=waypoints[i] -local point2=waypoints[i+1] -local x1=point1.x -local y1=point1.y -local x2=point2.x -local y2=point2.y -local d=math.sqrt((x1-x2)^2+(y1-y2)^2) -local heading=self:_Course(point1,point2) -total=total+d -text=text..string.format("Distance from WP %i-->%i = %6.1f km. Heading = %03d : %s - %s\n",i-1,i,d/1000,heading,self.waypointdescriptions[i],self.waypointdescriptions[i+1]) -end -text=text..string.format("Total distance = %6.1f km\n",total/1000) -text=text..string.format("******************************************************\n") -self:T2(RAT.id..text) -return total -end -function RAT:_TaskHolding(P1,Altitude,Speed,Duration) -local dx=3000 -local dy=0 -if self.category==RAT.cat.heli then -dx=200 -dy=0 -end -local P2={} -P2.x=P1.x+dx -P2.y=P1.y+dy -local Task={ -id='Orbit', -params={ -pattern=AI.Task.OrbitPattern.RACE_TRACK, -point=P1, -point2=P2, -speed=Speed, -altitude=Altitude -} -} -local DCSTask={} -DCSTask.id="ControlledTask" -DCSTask.params={} -DCSTask.params.task=Task -if self.ATCswitch then -local userflagname=string.format("%s#%03d",self.alias,self.SpawnIndex+1) -local maxholdingduration=60*120 -DCSTask.params.stopCondition={userFlag=userflagname,userFlagValue=1,duration=maxholdingduration} -else -DCSTask.params.stopCondition={duration=Duration} -end -return DCSTask -end -function RAT._WaypointFunction(group,rat,wp) -local Tnow=timer.getTime() -local sdx=rat:GetSpawnIndexFromGroup(group) -local departure=rat.ratcraft[sdx].departure:GetName() -local destination=rat.ratcraft[sdx].destination:GetName() -local landing=rat.ratcraft[sdx].landing -local WPholding=rat.ratcraft[sdx].wpholding -local WPfinal=rat.ratcraft[sdx].wpfinal -local text -text=string.format("Flight %s passing waypoint #%d %s.",group:GetName(),wp,rat.waypointdescriptions[wp]) -BASE.T(rat,RAT.id..text) -local status=rat.waypointstatus[wp] -rat:_SetStatus(group,status) -if wp==WPholding then -text=string.format("Flight %s to %s ATC: Holding and awaiting landing clearance.",group:GetName(),destination) -MESSAGE:New(text,10):ToAllIf(rat.reportstatus) -if rat.ATCswitch then -if rat.f10menu then -MENU_MISSION_COMMAND:New("Clear for landing",rat.Menu[rat.SubMenuName].groups[sdx],rat.ClearForLanding,rat,group:GetName()) -end -rat._ATCRegisterFlight(rat,group:GetName(),Tnow) -end -end -if wp==WPfinal then -text=string.format("Flight %s arrived at final destination %s.",group:GetName(),destination) -MESSAGE:New(text,10):ToAllIf(rat.reportstatus) -BASE.T(rat,RAT.id..text) -if landing==RAT.wp.air then -text=string.format("Activating despawn switch for flight %s! Group will be detroyed soon.",group:GetName()) -MESSAGE:New(text,10):ToAllIf(rat.Debug) -BASE.T(rat,RAT.id..text) -rat.ratcraft[sdx].despawnme=true -end -end -end -function RAT:_TaskFunction(FunctionString,...) -self:F2({FunctionString,arg}) -local DCSTask -local ArgumentKey -local templatename=self.templategroup:GetName() -local groupname=self:_AnticipatedGroupName() -local DCSScript={} -DCSScript[#DCSScript+1]="local MissionControllable = GROUP:FindByName(\""..groupname.."\") " -DCSScript[#DCSScript+1]="local RATtemplateControllable = GROUP:FindByName(\""..templatename.."\") " -if arg and arg.n>0 then -ArgumentKey='_'..tostring(arg):match("table: (.*)") -self.templategroup:SetState(self.templategroup,ArgumentKey,arg) -DCSScript[#DCSScript+1]="local Arguments = RATtemplateControllable:GetState(RATtemplateControllable, '"..ArgumentKey.."' ) " -DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable, unpack( Arguments ) )" -else -DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable )" -end -DCSTask=self.templategroup:TaskWrappedAction(self.templategroup:CommandDoScript(table.concat(DCSScript))) -return DCSTask -end -function RAT:_AnticipatedGroupName(index) -local index=index or self.SpawnIndex+1 -return string.format("%s#%03d",self.alias,index) -end -function RAT:_ActivateUncontrolled() -self:F() -local idx={} -local rat={} -local nactive=0 -for spawnindex,ratcraft in pairs(self.ratcraft)do -local group=ratcraft.group -if group and group:IsAlive()then -local text=string.format("Uncontrolled: Group = %s (spawnindex = %d), active = %s.",ratcraft.group:GetName(),spawnindex,tostring(ratcraft.active)) -self:T2(RAT.id..text) -if ratcraft.active then -nactive=nactive+1 -else -table.insert(idx,spawnindex) -end -end -end -local text=string.format("Uncontrolled: Ninactive = %d, Nactive = %d (of max %d).",#idx,nactive,self.activate_max) -self:T(RAT.id..text) -if#idx>0 and nactive=1 then -for i=1,nunits do -table.insert(parkingspots,spots[1].Coordinate) -table.insert(parkingindex,spots[1].TerminalID) -end -PointVec3=spots[1].Coordinate -else -_notenough=true -end -elseif spawnonairport then -if nfree>=nunits then -for i=1,nunits do -table.insert(parkingspots,spots[i].Coordinate) -table.insert(parkingindex,spots[i].TerminalID) -end -else -_notenough=true -end -end -if _notenough then -if self.respawn_inair and not self.SpawnUnControlled then -self:E(RAT.id..string.format("WARNING: Group %s has no parking spots at %s ==> air start!",self.SpawnTemplatePrefix,departure:GetName())) -spawnonground=false -spawnonship=false -spawnonfarp=false -spawnonrunway=false -waypoints[1].type=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -waypoints[1].action=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] -PointVec3.x=PointVec3.x+math.random(-1500,1500) -PointVec3.z=PointVec3.z+math.random(-1500,1500) -if self.category==RAT.cat.heli then -PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) -else -PointVec3.y=PointVec3:GetLandHeight()+math.random(500,3000) -end -else -self:E(RAT.id..string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,departure:GetName())) -return nil -end -end -else -end -for UnitID=1,nunits do -local UnitTemplate=SpawnTemplate.units[UnitID] -local SX=UnitTemplate.x -local SY=UnitTemplate.y -local BX=SpawnTemplate.route.points[1].x -local BY=SpawnTemplate.route.points[1].y -local TX=PointVec3.x+(SX-BX) -local TY=PointVec3.z+(SY-BY) -if spawnonground then -if spawnonship or spawnonfarp or spawnonrunway or automatic then -self:T(RAT.id..string.format("RAT group %s spawning at farp, ship or runway %s.",self.alias,departure:GetName())) -SpawnTemplate.units[UnitID].x=PointVec3.x -SpawnTemplate.units[UnitID].y=PointVec3.z -SpawnTemplate.units[UnitID].alt=PointVec3.y -else -self:T(RAT.id..string.format("RAT group %s spawning at airbase %s on parking spot id %d",self.alias,departure:GetName(),parkingindex[UnitID])) -SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x -SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z -SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y -end -else -self:T(RAT.id..string.format("RAT group %s spawning in air at %s.",self.alias,departure:GetName())) -SpawnTemplate.units[UnitID].x=TX -SpawnTemplate.units[UnitID].y=TY -SpawnTemplate.units[UnitID].alt=PointVec3.y -end -if self.Debug then -local unitspawn=COORDINATE:New(SpawnTemplate.units[UnitID].x,SpawnTemplate.units[UnitID].alt,SpawnTemplate.units[UnitID].y) -unitspawn:MarkToAll(string.format("RAT %s Spawnplace unit #%d",self.alias,UnitID)) -end -UnitTemplate.parking=nil -UnitTemplate.parking_id=nil -if parkingindex[UnitID]and not automatic then -UnitTemplate.parking=parkingindex[UnitID] -end -self:T2(RAT.id..string.format("RAT group %s unit number %d: Parking = %s",self.alias,UnitID,tostring(UnitTemplate.parking))) -self:T2(RAT.id..string.format("RAT group %s unit number %d: Parking ID = %s",self.alias,UnitID,tostring(UnitTemplate.parking_id))) -SpawnTemplate.units[UnitID].heading=heading -SpawnTemplate.units[UnitID].psi=-heading -if livery then -SpawnTemplate.units[UnitID].livery_id=livery -end -if self.actype then -SpawnTemplate.units[UnitID]["type"]=self.actype -end -SpawnTemplate.units[UnitID]["skill"]=self.skill -if self.onboardnum then -SpawnTemplate.units[UnitID]["onboard_num"]=string.format("%s%d%02d",self.onboardnum,(self.SpawnIndex-1)%10,(self.onboardnum0-1)+UnitID) -end -SpawnTemplate.CoalitionID=self.coalition -if self.country then -SpawnTemplate.CountryID=self.country -end -end -for i,wp in ipairs(waypoints)do -SpawnTemplate.route.points[i]=wp -end -SpawnTemplate.x=PointVec3.x -SpawnTemplate.y=PointVec3.z -if self.radio then -SpawnTemplate.communication=self.radio -end -if self.frequency then -SpawnTemplate.frequency=self.frequency -end -if self.modulation then -SpawnTemplate.modulation=self.modulation -end -self:T(SpawnTemplate) -end -end -return true -end -function RAT:_ATCInit(airports_map) -if not RAT.ATC.init then -local text -text="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay = "..RAT.ATC.delay -BASE:T(RAT.id..text) -RAT.ATC.init=true -for _,ap in pairs(airports_map)do -local name=ap:GetName() -RAT.ATC.airport[name]={} -RAT.ATC.airport[name].queue={} -RAT.ATC.airport[name].busy=false -RAT.ATC.airport[name].onfinal={} -RAT.ATC.airport[name].Nonfinal=0 -RAT.ATC.airport[name].traffic=0 -RAT.ATC.airport[name].Tlastclearance=nil -end -SCHEDULER:New(nil,RAT._ATCCheck,{self},5,15) -SCHEDULER:New(nil,RAT._ATCStatus,{self},5,60) -RAT.ATC.T0=timer.getTime() -end -end -function RAT:_ATCAddFlight(name,dest) -BASE:T(string.format("%sATC %s: Adding flight %s with destination %s.",RAT.id,dest,name,dest)) -RAT.ATC.flight[name]={} -RAT.ATC.flight[name].destination=dest -RAT.ATC.flight[name].Tarrive=-1 -RAT.ATC.flight[name].holding=-1 -RAT.ATC.flight[name].Tonfinal=-1 -end -function RAT:_ATCDelFlight(t,entry) -for k,_ in pairs(t)do -if k==entry then -t[entry]=nil -end -end -end -function RAT:_ATCRegisterFlight(name,time) -BASE:T(RAT.id.."Flight "..name.." registered at ATC for landing clearance.") -RAT.ATC.flight[name].Tarrive=time -RAT.ATC.flight[name].holding=0 -end -function RAT:_ATCStatus() -local Tnow=timer.getTime() -for name,_ in pairs(RAT.ATC.flight)do -local hold=RAT.ATC.flight[name].holding -local dest=RAT.ATC.flight[name].destination -if hold>=0 then -local busy="Runway state is unknown" -if RAT.ATC.airport[dest].Nonfinal>0 then -busy="Runway is occupied by "..RAT.ATC.airport[dest].Nonfinal -else -busy="Runway is currently clear" -end -local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.",dest,name,hold/60,hold%60,busy) -BASE:T(RAT.id..text) -elseif hold==RAT.ATC.onfinal then -local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal -local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.",dest,name,Tfinal/60,Tfinal%60) -BASE:T(RAT.id..text) -elseif hold==RAT.ATC.unregistered then -else -BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") -end -end -end -function RAT:_ATCCheck() -RAT:_ATCQueue() -local Tnow=timer.getTime() -for name,_ in pairs(RAT.ATC.airport)do -for qID,flight in ipairs(RAT.ATC.airport[name].queue)do -local nqueue=#RAT.ATC.airport[name].queue -local landing1 -if RAT.ATC.airport[name].Tlastclearance then -landing1=(Tnow-RAT.ATC.airport[name].Tlastclearance>RAT.ATC.delay)and RAT.ATC.airport[name].Nonfinal=0 then -RAT.ATC.flight[name].holding=Tnow-RAT.ATC.flight[name].Tarrive -end -local hold=RAT.ATC.flight[name].holding -local dest=RAT.ATC.flight[name].destination -if hold>=0 and airport==dest then -_queue[#_queue+1]={name,hold} -end -end -local function compare(a,b) -return a[2]>b[2] -end -table.sort(_queue,compare) -RAT.ATC.airport[airport].queue={} -for k,v in ipairs(_queue)do -table.insert(RAT.ATC.airport[airport].queue,v[1]) -end -end -end -RATMANAGER={ -ClassName="RATMANAGER", -Debug=false, -rat={}, -name={}, -alive={}, -planned={}, -min={}, -nrat=0, -ntot=nil, -Tcheck=60, -dTspawn=1.0, -manager=nil, -managerid=nil, -} -RATMANAGER.id="RATMANAGER | " -function RATMANAGER:New(ntot) -local self=BASE:Inherit(self,BASE:New()) -self.ntot=ntot or 1 -self:E(RATMANAGER.id..string.format("Creating manager for %d groups.",ntot)) -return self -end -function RATMANAGER:Add(ratobject,min) -ratobject.norespawn=true -ratobject.f10menu=false -self.nrat=self.nrat+1 -self.rat[self.nrat]=ratobject -self.alive[self.nrat]=0 -self.planned[self.nrat]=0 -self.name[self.nrat]=ratobject.alias -self.min[self.nrat]=min or 1 -self:T(RATMANAGER.id..string.format("Adding ratobject %s with min flights = %d",self.name[self.nrat],self.min[self.nrat])) -ratobject:Spawn(0) -return self -end -function RATMANAGER:Start(delay) -local delay=delay or 5 -local text=string.format(RATMANAGER.id.."RAT manager will be started in %d seconds.\n",delay) -text=text..string.format("Managed groups:\n") -for i=1,self.nrat do -text=text..string.format("- %s with min groups %d\n",self.name[i],self.min[i]) -end -text=text..string.format("Number of constantly alive groups %d",self.ntot) -self:E(text) -SCHEDULER:New(nil,self._Start,{self},delay) -return self -end -function RATMANAGER:_Start() -local n=0 -for i=1,self.nrat do -n=n+self.min[i] -end -self.ntot=math.max(self.ntot,n) -local N=self:_RollDice(self.nrat,self.ntot,self.min,self.alive) -local time=0.0 -for i=1,self.nrat do -for j=1,N[i]do -time=time+self.dTspawn -SCHEDULER:New(nil,RAT._SpawnWithRoute,{self.rat[i]},time) -end -end -for i=1,self.nrat do -if self.rat[i].uncontrolled and self.rat[i].activate_uncontrolled then -local Tactivate=math.max(time+1,self.rat[i].activate_delay) -SCHEDULER:New(self.rat[i],self.rat[i]._ActivateUncontrolled,{self.rat[i]},Tactivate,self.rat[i].activate_delta,self.rat[i].activate_frand) -end -end -local TstartManager=math.max(time+1,self.Tcheck) -self.manager,self.managerid=SCHEDULER:New(self,self._Manage,{self},TstartManager,self.Tcheck) -local text=string.format(RATMANAGER.id.."Starting RAT manager with scheduler ID %s in %d seconds. Repeat interval %d seconds.",self.managerid,TstartManager,self.Tcheck) -self:E(text) -return self -end -function RATMANAGER:Stop(delay) -delay=delay or 1 -self:E(string.format(RATMANAGER.id.."Manager will be stopped in %d seconds.",delay)) -SCHEDULER:New(nil,self._Stop,{self},delay) -return self -end -function RATMANAGER:_Stop() -self:E(string.format(RATMANAGER.id.."Stopping manager with scheduler ID %s.",self.managerid)) -self.manager:Stop(self.managerid) -return self -end -function RATMANAGER:SetTcheck(dt) -self.Tcheck=dt or 60 -return self -end -function RATMANAGER:SetTspawn(dt) -self.dTspawn=dt or 1.0 -return self -end -function RATMANAGER:_Manage() -local ntot=self:_Count() -local text=string.format("Number of alive groups %d. New groups to be spawned %d.",ntot,self.ntot-ntot) -self:T(RATMANAGER.id..text) -local N=self:_RollDice(self.nrat,self.ntot,self.min,self.alive) -local time=0.0 -for i=1,self.nrat do -for j=1,N[i]do -time=time+self.dTspawn -self.planned[i]=self.planned[i]+1 -SCHEDULER:New(nil,RATMANAGER._Spawn,{self,i},time) -end -end -end -function RATMANAGER:_Spawn(i) -local rat=self.rat[i] -rat:_SpawnWithRoute() -self.planned[i]=self.planned[i]-1 -end -function RATMANAGER:_Count() -local ntotal=0 -for i=1,self.nrat do -local n=0 -local ratobject=self.rat[i] -for spawnindex,ratcraft in pairs(ratobject.ratcraft)do -local group=ratcraft.group -if group and group:IsAlive()then -n=n+1 -end -end -self.alive[i]=n -ntotal=ntotal+n -local text=string.format("Number of alive groups of %s = %d, planned=%d",self.name[i],n,self.planned[i]) -self:T(RATMANAGER.id..text) -end -return ntotal -end -function RATMANAGER:_RollDice(nrat,ntot,min,alive) -local function sum(A,index) -local summe=0 -for _,i in ipairs(index)do -summe=summe+A[i] -end -return summe -end -local N={} -local M={} -local P={} -for i=1,nrat do -local a=alive[i]+self.planned[i] -N[#N+1]=0 -M[#M+1]=math.max(a,min[i]) -P[#P+1]=math.max(min[i]-a,0) -end -local mini={} -local maxi={} -local rattab={} -for i=1,nrat do -table.insert(rattab,i) -end -local done={} -local nnew=ntot -for i=1,nrat do -nnew=nnew-alive[i]-self.planned[i] -end -for i=1,nrat-1 do -local r=math.random(#rattab) -local j=rattab[r] -table.remove(rattab,r) -table.insert(done,j) -local sN=sum(N,done) -local sP=sum(P,rattab) -maxi[j]=nnew-sN-sP -mini[j]=P[j] -if maxi[j]>=mini[j]then -N[j]=math.random(mini[j],maxi[j]) -else -N[j]=0 -end -self:T3(string.format("RATMANAGER: i=%d, alive=%d, planned=%d, min=%d, mini=%d, maxi=%d, add=%d, sumN=%d, sumP=%d",j,alive[j],self.planned[i],min[j],mini[j],maxi[j],N[j],sN,sP)) -end -local j=rattab[1] -N[j]=nnew-sum(N,done) -mini[j]=nnew-sum(N,done) -maxi[j]=nnew-sum(N,done) -table.remove(rattab,1) -table.insert(done,j) -local text=RATMANAGER.id.."\n" -for i=1,nrat do -text=text..string.format("%s: i=%d, alive=%d, planned=%d, min=%d, mini=%d, maxi=%d, add=%d\n",self.name[i],i,alive[i],self.planned[i],min[i],mini[i],maxi[i],N[i]) -end -text=text..string.format("Total # of groups to add = %d",sum(N,done)) -self:T(text) -return N -end -RANGE={ -ClassName="RANGE", -Debug=false, -verbose=0, -id=nil, -rangename=nil, -location=nil, -messages=true, -rangeradius=5000, -rangezone=nil, -strafeTargets={}, -bombingTargets={}, -nbombtargets=0, -nstrafetargets=0, -MenuAddedTo={}, -planes={}, -strafeStatus={}, -strafePlayerResults={}, -bombPlayerResults={}, -PlayerSettings={}, -dtBombtrack=0.005, -BombtrackThreshold=25000, -Tmsg=30, -examinergroupname=nil, -examinerexclusive=nil, -strafemaxalt=914, -ndisplayresult=10, -BombSmokeColor=SMOKECOLOR.Red, -StrafeSmokeColor=SMOKECOLOR.Green, -StrafePitSmokeColor=SMOKECOLOR.White, -illuminationminalt=500, -illuminationmaxalt=1000, -scorebombdistance=1000, -TdelaySmoke=3.0, -trackbombs=true, -trackrockets=true, -trackmissiles=true, -defaultsmokebomb=true, -autosave=false, -instructorfreq=nil, -instructor=nil, -rangecontrolfreq=nil, -rangecontrol=nil, -soundpath="Range Soundfiles/", -targetsheet=nil, -targetpath=nil, -targetprefix=nil, -Coalition=nil, -} -RANGE.Defaults={ -goodhitrange=25, -strafemaxalt=914, -dtBombtrack=0.005, -Tmsg=30, -ndisplayresult=10, -rangeradius=5000, -TdelaySmoke=3.0, -boxlength=3000, -boxwidth=300, -goodpass=20, -foulline=610 -} -RANGE.TargetType={ -UNIT="Unit", -STATIC="Static", -COORD="Coordinate", -SCENERY="Scenery" -} -RANGE.Sound={ -RC0={filename="RC-0.ogg",duration=0.60}, -RC1={filename="RC-1.ogg",duration=0.47}, -RC2={filename="RC-2.ogg",duration=0.43}, -RC3={filename="RC-3.ogg",duration=0.50}, -RC4={filename="RC-4.ogg",duration=0.58}, -RC5={filename="RC-5.ogg",duration=0.54}, -RC6={filename="RC-6.ogg",duration=0.61}, -RC7={filename="RC-7.ogg",duration=0.53}, -RC8={filename="RC-8.ogg",duration=0.34}, -RC9={filename="RC-9.ogg",duration=0.54}, -RCAccuracy={filename="RC-Accuracy.ogg",duration=0.67}, -RCDegrees={filename="RC-Degrees.ogg",duration=0.59}, -RCExcellentHit={filename="RC-ExcellentHit.ogg",duration=0.76}, -RCExcellentPass={filename="RC-ExcellentPass.ogg",duration=0.89}, -RCFeet={filename="RC-Feet.ogg",duration=0.49}, -RCFor={filename="RC-For.ogg",duration=0.64}, -RCGoodHit={filename="RC-GoodHit.ogg",duration=0.52}, -RCGoodPass={filename="RC-GoodPass.ogg",duration=0.62}, -RCHitsOnTarget={filename="RC-HitsOnTarget.ogg",duration=0.88}, -RCImpact={filename="RC-Impact.ogg",duration=0.61}, -RCIneffectiveHit={filename="RC-IneffectiveHit.ogg",duration=0.86}, -RCIneffectivePass={filename="RC-IneffectivePass.ogg",duration=0.99}, -RCInvalidHit={filename="RC-InvalidHit.ogg",duration=2.97}, -RCLeftStrafePitTooQuickly={filename="RC-LeftStrafePitTooQuickly.ogg",duration=3.09}, -RCPercent={filename="RC-Percent.ogg",duration=0.56}, -RCPoorHit={filename="RC-PoorHit.ogg",duration=0.54}, -RCPoorPass={filename="RC-PoorPass.ogg",duration=0.68}, -RCRollingInOnStrafeTarget={filename="RC-RollingInOnStrafeTarget.ogg",duration=1.38}, -RCTotalRoundsFired={filename="RC-TotalRoundsFired.ogg",duration=1.22}, -RCWeaponImpactedTooFar={filename="RC-WeaponImpactedTooFar.ogg",duration=3.73}, -IR0={filename="IR-0.ogg",duration=0.55}, -IR1={filename="IR-1.ogg",duration=0.41}, -IR2={filename="IR-2.ogg",duration=0.37}, -IR3={filename="IR-3.ogg",duration=0.41}, -IR4={filename="IR-4.ogg",duration=0.37}, -IR5={filename="IR-5.ogg",duration=0.43}, -IR6={filename="IR-6.ogg",duration=0.55}, -IR7={filename="IR-7.ogg",duration=0.43}, -IR8={filename="IR-8.ogg",duration=0.38}, -IR9={filename="IR-9.ogg",duration=0.55}, -IRDecimal={filename="IR-Decimal.ogg",duration=0.54}, -IRMegaHertz={filename="IR-MegaHertz.ogg",duration=0.87}, -IREnterRange={filename="IR-EnterRange.ogg",duration=4.83}, -IRExitRange={filename="IR-ExitRange.ogg",duration=3.10}, -} -RANGE.Names={} -RANGE.MenuF10={} -RANGE.MenuF10Root=nil -RANGE.version="2.7.3" -function RANGE:New(RangeName,Coalition) -local self=BASE:Inherit(self,FSM:New()) -self.rangename=RangeName or"Practice Range" -self.Coalition=Coalition -self.lid=string.format("RANGE %s | ",self.rangename) -local text=string.format("Script version %s - creating new RANGE object %s.",RANGE.version,self.rangename) -self:I(self.lid..text) -self:SetDefaultPlayerSmokeBomb() -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","Status","*") -self:AddTransition("*","Impact","*") -self:AddTransition("*","RollingIn","*") -self:AddTransition("*","StrafeResult","*") -self:AddTransition("*","EnterRange","*") -self:AddTransition("*","ExitRange","*") -self:AddTransition("*","Save","*") -self:AddTransition("*","Load","*") -return self -end -function RANGE:onafterStart() -local _location=nil -local _count=0 -for _,_target in pairs(self.bombingTargets)do -_count=_count+1 -if _location==nil then -_location=self:_GetBombTargetCoordinate(_target) -end -end -self.nbombtargets=_count -_count=0 -for _,_target in pairs(self.strafeTargets)do -_count=_count+1 -for _,_unit in pairs(_target.targets)do -if _location==nil then -_location=_unit:GetCoordinate() -end -end -end -self.nstrafetargets=_count -if self.location==nil then -self.location=_location -end -if self.location==nil then -local text=string.format("ERROR! No range location found. Number of strafe targets = %d. Number of bomb targets = %d.",self.nstrafetargets,self.nbombtargets) -self:E(self.lid..text) -return -end -if self.rangezone==nil then -self.rangezone=ZONE_RADIUS:New(self.rangename,{x=self.location.x,y=self.location.z},self.rangeradius) -end -local text=string.format("Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.",self.rangename,self.nstrafetargets,self.nbombtargets) -self:I(self.lid..text) -self:HandleEvent(EVENTS.Birth) -self:HandleEvent(EVENTS.Hit) -self:HandleEvent(EVENTS.Shot) -for _,_target in pairs(self.bombingTargets)do -local target=_target -if target.move and target.type==RANGE.TargetType.UNIT and target.speed>1 then -target.target:PatrolZones({self.rangezone},target.speed*0.75,ENUMS.Formation.Vehicle.OffRoad) -end -end -if self.rangecontrolfreq and not self.useSRS then -self.rangecontrol=RADIOQUEUE:New(self.rangecontrolfreq,nil,self.rangename) -self.rangecontrol.schedonce=true -self.rangecontrol:SetDigit(0,RANGE.Sound.RC0.filename,RANGE.Sound.RC0.duration,self.soundpath) -self.rangecontrol:SetDigit(1,RANGE.Sound.RC1.filename,RANGE.Sound.RC1.duration,self.soundpath) -self.rangecontrol:SetDigit(2,RANGE.Sound.RC2.filename,RANGE.Sound.RC2.duration,self.soundpath) -self.rangecontrol:SetDigit(3,RANGE.Sound.RC3.filename,RANGE.Sound.RC3.duration,self.soundpath) -self.rangecontrol:SetDigit(4,RANGE.Sound.RC4.filename,RANGE.Sound.RC4.duration,self.soundpath) -self.rangecontrol:SetDigit(5,RANGE.Sound.RC5.filename,RANGE.Sound.RC5.duration,self.soundpath) -self.rangecontrol:SetDigit(6,RANGE.Sound.RC6.filename,RANGE.Sound.RC6.duration,self.soundpath) -self.rangecontrol:SetDigit(7,RANGE.Sound.RC7.filename,RANGE.Sound.RC7.duration,self.soundpath) -self.rangecontrol:SetDigit(8,RANGE.Sound.RC8.filename,RANGE.Sound.RC8.duration,self.soundpath) -self.rangecontrol:SetDigit(9,RANGE.Sound.RC9.filename,RANGE.Sound.RC9.duration,self.soundpath) -self.rangecontrol:SetSenderCoordinate(self.location) -self.rangecontrol:SetSenderUnitName(self.rangecontrolrelayname) -self.rangecontrol:Start(1,0.1) -if self.instructorfreq and not self.useSRS then -self.instructor=RADIOQUEUE:New(self.instructorfreq,nil,self.rangename) -self.instructor.schedonce=true -self.instructor:SetDigit(0,RANGE.Sound.IR0.filename,RANGE.Sound.IR0.duration,self.soundpath) -self.instructor:SetDigit(1,RANGE.Sound.IR1.filename,RANGE.Sound.IR1.duration,self.soundpath) -self.instructor:SetDigit(2,RANGE.Sound.IR2.filename,RANGE.Sound.IR2.duration,self.soundpath) -self.instructor:SetDigit(3,RANGE.Sound.IR3.filename,RANGE.Sound.IR3.duration,self.soundpath) -self.instructor:SetDigit(4,RANGE.Sound.IR4.filename,RANGE.Sound.IR4.duration,self.soundpath) -self.instructor:SetDigit(5,RANGE.Sound.IR5.filename,RANGE.Sound.IR5.duration,self.soundpath) -self.instructor:SetDigit(6,RANGE.Sound.IR6.filename,RANGE.Sound.IR6.duration,self.soundpath) -self.instructor:SetDigit(7,RANGE.Sound.IR7.filename,RANGE.Sound.IR7.duration,self.soundpath) -self.instructor:SetDigit(8,RANGE.Sound.IR8.filename,RANGE.Sound.IR8.duration,self.soundpath) -self.instructor:SetDigit(9,RANGE.Sound.IR9.filename,RANGE.Sound.IR9.duration,self.soundpath) -self.instructor:SetSenderCoordinate(self.location) -self.instructor:SetSenderUnitName(self.instructorrelayname) -self.instructor:Start(1,0.1) -end -end -if self.autosave then -self:Load() -end -if self.Debug then -self:_MarkTargetsOnMap() -self:_SmokeBombTargets() -self:_SmokeStrafeTargets() -self:_SmokeStrafeTargetBoxes() -self.rangezone:SmokeZone(SMOKECOLOR.White) -end -self:__Status(-60) -end -function RANGE:SetMaxStrafeAlt(maxalt) -self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt -return self -end -function RANGE:SetBombtrackTimestep(dt) -self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack -return self -end -function RANGE:SetMessageTimeDuration(time) -self.Tmsg=time or RANGE.Defaults.Tmsg -return self -end -function RANGE:SetAutosaveOn() -self.autosave=true -return self -end -function RANGE:SetAutosaveOff() -self.autosave=false -return self -end -function RANGE:SetTargetSheet(path,prefix) -if io then -self.targetsheet=true -self.targetpath=path -self.targetprefix=prefix -else -self:E(self.lid.."ERROR: io is not desanitized. Cannot save target sheet.") -end -return self -end -function RANGE:SetFunkManOn(Port,Host) -self.funkmanSocket=SOCKET:New(Port,Host) -return self -end -function RANGE:SetMessageToExaminer(examinergroupname,exclusively) -self.examinergroupname=examinergroupname -self.examinerexclusive=exclusively -return self -end -function RANGE:SetDisplayedMaxPlayerResults(nmax) -self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult -return self -end -function RANGE:SetRangeRadius(radius) -self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius -return self -end -function RANGE:SetDefaultPlayerSmokeBomb(switch) -if switch==true or switch==nil then -self.defaultsmokebomb=true -else -self.defaultsmokebomb=false -end -return self -end -function RANGE:SetBombtrackThreshold(distance) -self.BombtrackThreshold=(distance or 25)*1000 -return self -end -function RANGE:SetRangeLocation(coordinate) -self.location=coordinate -return self -end -function RANGE:SetRangeZone(zone) -self.rangezone=zone -return self -end -function RANGE:SetBombTargetSmokeColor(colorid) -self.BombSmokeColor=colorid or SMOKECOLOR.Red -return self -end -function RANGE:SetScoreBombDistance(distance) -self.scorebombdistance=distance or 1000 -return self -end -function RANGE:SetStrafeTargetSmokeColor(colorid) -self.StrafeSmokeColor=colorid or SMOKECOLOR.Green -return self -end -function RANGE:SetStrafePitSmokeColor(colorid) -self.StrafePitSmokeColor=colorid or SMOKECOLOR.White -return self -end -function RANGE:SetSmokeTimeDelay(delay) -self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke -return self -end -function RANGE:DebugON() -self.Debug=true -return self -end -function RANGE:DebugOFF() -self.Debug=false -return self -end -function RANGE:SetMessagesOFF() -self.messages=false -return self -end -function RANGE:SetMessagesON() -self.messages=true -return self -end -function RANGE:TrackBombsON() -self.trackbombs=true -return self -end -function RANGE:TrackBombsOFF() -self.trackbombs=false -return self -end -function RANGE:TrackRocketsON() -self.trackrockets=true -return self -end -function RANGE:TrackRocketsOFF() -self.trackrockets=false -return self -end -function RANGE:TrackMissilesON() -self.trackmissiles=true -return self -end -function RANGE:TrackMissilesOFF() -self.trackmissiles=false -return self -end -function RANGE:SetSRS(PathToSRS,Port,Coalition,Frequency,Modulation,Volume,PathToGoogleKey) -if PathToSRS or MSRS.path then -self.useSRS=true -self.controlmsrs=MSRS:New(PathToSRS or MSRS.path,Frequency or 256,Modulation or radio.modulation.AM) -self.controlmsrs:SetPort(Port or MSRS.port) -self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE) -self.controlmsrs:SetLabel("RANGEC") -self.controlmsrs:SetVolume(Volume or 1.0) -self.controlsrsQ=MSRSQUEUE:New("CONTROL") -self.instructmsrs=MSRS:New(PathToSRS or MSRS.path,Frequency or 305,Modulation or radio.modulation.AM) -self.instructmsrs:SetPort(Port or MSRS.port) -self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE) -self.instructmsrs:SetLabel("RANGEI") -self.instructmsrs:SetVolume(Volume or 1.0) -self.instructsrsQ=MSRSQUEUE:New("INSTRUCT") -if PathToGoogleKey then -self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) -self.controlmsrs:SetProvider(MSRS.Provider.GOOGLE) -self.instructmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) -self.instructmsrs:SetProvider(MSRS.Provider.GOOGLE) -end -else -self:E(self.lid..string.format("ERROR: No SRS path specified!")) -end -return self -end -function RANGE:SetSRSRangeControl(frequency,modulation,voice,culture,gender,relayunitname) -if not self.instructmsrs then -self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeControl!") -return self -end -self.rangecontrolfreq=frequency or 256 -self.controlmsrs:SetFrequencies(self.rangecontrolfreq) -self.controlmsrs:SetModulations(modulation or radio.modulation.AM) -self.controlmsrs:SetVoice(voice) -self.controlmsrs:SetCulture(culture or"en-US") -self.controlmsrs:SetGender(gender or"female") -self.rangecontrol=true -if relayunitname then -local unit=UNIT:FindByName(relayunitname) -local Coordinate=unit:GetCoordinate() -self.rangecontrolrelayname=relayunitname -end -return self -end -function RANGE:SetSRSRangeInstructor(frequency,modulation,voice,culture,gender,relayunitname) -if not self.instructmsrs then -self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeInstructor!") -return self -end -self.instructorfreq=frequency or 305 -self.instructmsrs:SetFrequencies(self.instructorfreq) -self.instructmsrs:SetModulations(modulation or radio.modulation.AM) -self.instructmsrs:SetVoice(voice) -self.instructmsrs:SetCulture(culture or"en-US") -self.instructmsrs:SetGender(gender or"male") -self.instructor=true -if relayunitname then -local unit=UNIT:FindByName(relayunitname) -local Coordinate=unit:GetCoordinate() -self.instructmsrs:SetCoordinate(Coordinate) -self.instructorrelayname=relayunitname -end -return self -end -function RANGE:SetRangeControl(frequency,relayunitname) -self.rangecontrolfreq=frequency or 256 -self.rangecontrolrelayname=relayunitname -return self -end -function RANGE:SetInstructorRadio(frequency,relayunitname) -self.instructorfreq=frequency or 305 -self.instructorrelayname=relayunitname -return self -end -function RANGE:SetSoundfilesPath(path) -self.soundpath=tostring(path or"Range Soundfiles/") -self:I(self.lid..string.format("Setting sound files path to %s",self.soundpath)) -return self -end -function RANGE:AddStrafePit(targetnames,boxlength,boxwidth,heading,inverseheading,goodpass,foulline) -self:F({targetnames=targetnames,boxlength=boxlength,boxwidth=boxwidth,heading=heading,inverseheading=inverseheading,goodpass=goodpass,foulline=foulline}) -if type(targetnames)~="table"then -targetnames={targetnames} -end -local _targets={} -local center=nil -local ntargets=0 -for _i,_name in ipairs(targetnames)do -local _isstatic=self:_CheckStatic(_name) -local unit=nil -if _isstatic==true then -self:T(self.lid..string.format("Adding STATIC object %s as strafe target #%d.",_name,_i)) -unit=STATIC:FindByName(_name,false) -elseif _isstatic==false then -self:T(self.lid..string.format("Adding UNIT object %s as strafe target #%d.",_name,_i)) -unit=UNIT:FindByName(_name) -else -local text=string.format("ERROR! Could not find ANY strafe target object with name %s.",_name) -self:E(self.lid..text) -end -if unit then -table.insert(_targets,unit) -if center==nil then -center=unit -end -ntargets=ntargets+1 -end -end -if ntargets==0 then -local text=string.format("ERROR! No strafe target could be found when calling RANGE:AddStrafePit() for range %s",self.rangename) -self:E(self.lid..text) -return -end -local l=boxlength or RANGE.Defaults.boxlength -local w=(boxwidth or RANGE.Defaults.boxwidth)/2 -local heading=heading or center:GetHeading() -if inverseheading~=nil then -if inverseheading then -heading=heading-180 -end -end -if heading<0 then -heading=heading+360 -end -if heading>360 then -heading=heading-360 -end -goodpass=goodpass or RANGE.Defaults.goodpass -foulline=foulline or RANGE.Defaults.foulline -local Ccenter=center:GetCoordinate() -local _name=center:GetName() -local p={} -p[#p+1]=Ccenter:Translate(w,heading+90) -p[#p+1]=p[#p]:Translate(l,heading) -p[#p+1]=p[#p]:Translate(2*w,heading-90) -p[#p+1]=p[#p]:Translate(-l,heading) -local pv2={} -for i,p in ipairs(p)do -pv2[i]={x=p.x,y=p.z} -end -local _polygon=ZONE_POLYGON_BASE:New(_name,pv2) -local st={} -st.name=_name -st.polygon=_polygon -st.coordinate=Ccenter -st.goodPass=goodpass -st.targets=_targets -st.foulline=foulline -st.smokepoints=p -st.heading=heading -table.insert(self.strafeTargets,st) -local text=string.format("Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f",_name,ntargets,heading,l,w,goodpass,foulline) -self:T(self.lid..text) -return self -end -function RANGE:AddStrafePitGroup(group,boxlength,boxwidth,heading,inverseheading,goodpass,foulline) -self:F({group=group,boxlength=boxlength,boxwidth=boxwidth,heading=heading,inverseheading=inverseheading,goodpass=goodpass,foulline=foulline}) -if group and group:IsAlive()then -local _units=group:GetUnits() -local _names={} -for _,_unit in ipairs(_units)do -local _unit=_unit -if _unit and _unit:IsAlive()then -local _name=_unit:GetName() -table.insert(_names,_name) -end -end -self:AddStrafePit(_names,boxlength,boxwidth,heading,inverseheading,goodpass,foulline) -end -return self -end -function RANGE:AddBombingTargets(targetnames,goodhitrange,randommove) -self:F({targetnames=targetnames,goodhitrange=goodhitrange,randommove=randommove}) -if type(targetnames)~="table"then -targetnames={targetnames} -end -goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange -for _,name in pairs(targetnames)do -local _isstatic=self:_CheckStatic(name) -if _isstatic==true then -local _static=STATIC:FindByName(name) -self:T2(self.lid..string.format("Adding static bombing target %s with hit range %d.",name,goodhitrange,false)) -self:AddBombingTargetUnit(_static,goodhitrange) -elseif _isstatic==false then -local _unit=UNIT:FindByName(name) -self:T2(self.lid..string.format("Adding unit bombing target %s with hit range %d.",name,goodhitrange,randommove)) -self:AddBombingTargetUnit(_unit,goodhitrange,randommove) -else -self:E(self.lid..string.format("ERROR! Could not find bombing target %s.",name)) -end -end -return self -end -function RANGE:AddBombingTargetUnit(unit,goodhitrange,randommove) -self:F({unit=unit,goodhitrange=goodhitrange,randommove=randommove}) -local name=unit:GetName() -local _isstatic=self:_CheckStatic(name) -goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange -if randommove==nil or _isstatic==true then -randommove=false -end -if _isstatic==true then -self:I(self.lid..string.format("Adding STATIC bombing target %s with good hit range %d. Random move = %s.",name,goodhitrange,tostring(randommove))) -elseif _isstatic==false then -self:I(self.lid..string.format("Adding UNIT bombing target %s with good hit range %d. Random move = %s.",name,goodhitrange,tostring(randommove))) -else -self:E(self.lid..string.format("ERROR! No bombing target with name %s could be found. Carefully check all UNIT and STATIC names defined in the mission editor!",name)) -end -local speed=0 -if _isstatic==false then -speed=self:_GetSpeed(unit) -end -local target={} -target.name=name -target.target=unit -target.goodhitrange=goodhitrange -target.move=randommove -target.speed=speed -target.coordinate=unit:GetCoordinate() -if _isstatic then -target.type=RANGE.TargetType.STATIC -else -target.type=RANGE.TargetType.UNIT -end -table.insert(self.bombingTargets,target) -return self -end -function RANGE:AddBombingTargetCoordinate(coord,name,goodhitrange) -local target={} -target.name=name or"Bomb Target" -target.target=nil -target.goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange -target.move=false -target.speed=0 -target.coordinate=coord -target.type=RANGE.TargetType.COORD -table.insert(self.bombingTargets,target) -return self -end -function RANGE:AddBombingTargetScenery(scenery,goodhitrange) -local name=scenery:GetName() -goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange -if name then -self:I(self.lid..string.format("Adding SCENERY bombing target %s with good hit range %d",name,goodhitrange)) -else -self:E(self.lid..string.format("ERROR! No bombing target with name %s could be found!",name)) -end -local target={} -target.name=name -target.target=scenery -target.goodhitrange=goodhitrange -target.move=false -target.speed=0 -target.coordinate=scenery:GetCoordinate() -target.type=RANGE.TargetType.SCENERY -table.insert(self.bombingTargets,target) -return self -end -function RANGE:AddBombingTargetGroup(group,goodhitrange,randommove) -self:F({group=group,goodhitrange=goodhitrange,randommove=randommove}) -if group then -local _units=group:GetUnits() -for _,_unit in pairs(_units)do -if _unit and _unit:IsAlive()then -self:AddBombingTargetUnit(_unit,goodhitrange,randommove) -end -end -end -return self -end -function RANGE:GetFoullineDistance(namepit,namefoulline) -self:F({namepit=namepit,namefoulline=namefoulline}) -local _staticpit=self:_CheckStatic(namepit) -local _staticfoul=self:_CheckStatic(namefoulline) -local pit=nil -if _staticpit==true then -pit=STATIC:FindByName(namepit,false) -elseif _staticpit==false then -pit=UNIT:FindByName(namepit) -else -self:E(self.lid..string.format("ERROR! Pit object %s could not be found in GetFoullineDistance function. Check the name in the ME.",namepit)) -end -local foul=nil -if _staticfoul==true then -foul=STATIC:FindByName(namefoulline,false) -elseif _staticfoul==false then -foul=UNIT:FindByName(namefoulline) -else -self:E(self.lid..string.format("ERROR! Foul line object %s could not be found in GetFoullineDistance function. Check the name in the ME.",namefoulline)) -end -local fouldist=0 -if pit~=nil and foul~=nil then -fouldist=pit:GetCoordinate():Get2DDistance(foul:GetCoordinate()) -else -self:E(self.lid..string.format("ERROR! Foul line distance could not be determined. Check pit object name %s and foul line object name %s in the ME.",namepit,namefoulline)) -end -self:T(self.lid..string.format("Foul line distance = %.1f m.",fouldist)) -return fouldist -end -function RANGE:OnEventBirth(EventData) -self:F({eventbirth=EventData}) -if not EventData.IniPlayerName then return end -local _unitName=EventData.IniUnitName -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -self:T3(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName)) -self:T3(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName)) -self:T3(self.lid.."BIRTH: player = "..tostring(_playername)) -if _unit and _playername then -local _uid=_unit:GetID() -local _group=_unit:GetGroup() -local _gid=_group:GetID() -local _callsign=_unit:GetCallsign() -local text=string.format("Player %s, callsign %s entered unit %s (UID %d) of group %s (GID %d)",_playername,_callsign,_unitName,_uid,_group:GetName(),_gid) -self:T(self.lid..text) -self.strafeStatus[_uid]=nil -if self.Coalition then -if EventData.IniCoalition==self.Coalition then -self:ScheduleOnce(0.1,self._AddF10Commands,self,_unitName) -end -else -self:ScheduleOnce(0.1,self._AddF10Commands,self,_unitName) -end -self.PlayerSettings[_playername]={} -self.PlayerSettings[_playername].smokebombimpact=self.defaultsmokebomb -self.PlayerSettings[_playername].flaredirecthits=false -self.PlayerSettings[_playername].smokecolor=SMOKECOLOR.Blue -self.PlayerSettings[_playername].flarecolor=FLARECOLOR.Red -self.PlayerSettings[_playername].delaysmoke=true -self.PlayerSettings[_playername].messages=true -self.PlayerSettings[_playername].client=CLIENT:FindByName(_unitName,nil,true) -self.PlayerSettings[_playername].unitname=_unitName -self.PlayerSettings[_playername].unit=_unit -self.PlayerSettings[_playername].playername=_playername -self.PlayerSettings[_playername].airframe=EventData.IniUnit:GetTypeName() -self.PlayerSettings[_playername].inzone=false -if self.planes[_uid]~=true then -self.timerCheckZone=TIMER:New(self._CheckInZone,self,EventData.IniUnitName):Start(1,1) -self.planes[_uid]=true -end -end -end -function RANGE:OnEventHit(EventData) -self:F({eventhit=EventData}) -self:T3(self.lid.."HIT: Ini unit = "..tostring(EventData.IniUnitName)) -self:T3(self.lid.."HIT: Ini group = "..tostring(EventData.IniGroupName)) -self:T3(self.lid.."HIT: Tgt target = "..tostring(EventData.TgtUnitName)) -local _unitName=EventData.IniUnitName -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit==nil or _playername==nil then -return -end -local _unitID=_unit:GetID() -local target=EventData.TgtUnit -local targetname=EventData.TgtUnitName -local _currentTarget=self.strafeStatus[_unitID] -if _currentTarget and target:IsAlive()then -local playerPos=_unit:GetCoordinate() -local targetPos=target:GetCoordinate() -for _,_target in pairs(_currentTarget.zone.targets)do -if _target and _target:IsAlive()and _target:GetName()==targetname then -local dist=playerPos:Get2DDistance(targetPos) -if dist>_currentTarget.zone.foulline then -_currentTarget.hits=_currentTarget.hits+1 -if _unit and _playername and self.PlayerSettings[_playername].flaredirecthits then -targetPos:Flare(self.PlayerSettings[_playername].flarecolor) -end -else -if _currentTarget.pastfoulline==false and _unit and _playername then -local _d=_currentTarget.zone.foulline -local text=string.format("%s, Invalid hit!\nYou already passed foul line distance of %d m for target %s.",self:_myname(_unitName),_d,targetname) -if self.useSRS then -local ttstext=string.format("%s, Invalid hit! You already passed foul line distance of %d meters for target %s.",self:_myname(_unitName),_d,targetname) -self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2) -end -self:_DisplayMessageToGroup(_unit,text) -self:T2(self.lid..text) -_currentTarget.pastfoulline=true -end -end -end -end -end -for _,_bombtarget in pairs(self.bombingTargets)do -local _target=_bombtarget.target -if _target and _target:IsAlive()and _bombtarget.name==targetname then -if _unit and _playername then -if self.PlayerSettings[_playername].flaredirecthits then -local targetPos=_target:GetCoordinate() -targetPos:Flare(self.PlayerSettings[_playername].flarecolor) -end -end -end -end -end -function RANGE._OnImpact(weapon,self,playerData,attackHdg,attackAlt,attackVel) -local _closetTarget=nil -local _distance=nil -local _closeCoord=nil -local _hitquality="POOR" -local _callsign=self:_myname(playerData.unitname) -local _playername=playerData.playername -local _unit=playerData.unit -local impactcoord=weapon:GetImpactCoordinate() -local insidezone=self.rangezone:IsCoordinateInZone(impactcoord) -if playerData.smokebombimpact and insidezone then -if playerData.delaysmoke then -timer.scheduleFunction(self._DelayedSmoke,{coord=impactcoord,color=playerData.smokecolor},timer.getTime()+self.TdelaySmoke) -else -impactcoord:Smoke(playerData.smokecolor) -end -end -for _,_bombtarget in pairs(self.bombingTargets)do -local bombtarget=_bombtarget -local targetcoord=self:_GetBombTargetCoordinate(_bombtarget) -if targetcoord then -local _temp=impactcoord:Get2DDistance(targetcoord) -if _distance==nil or _temp<_distance then -_distance=_temp -_closetTarget=bombtarget -_closeCoord=targetcoord -if _distance<=1.53 then -_hitquality="SHACK" -elseif _distance<=0.5*bombtarget.goodhitrange then -_hitquality="EXCELLENT" -elseif _distance<=bombtarget.goodhitrange then -_hitquality="GOOD" -elseif _distance<=2*bombtarget.goodhitrange then -_hitquality="INEFFECTIVE" -else -_hitquality="POOR" -end -end -end -end -if _distance and _distance<=self.scorebombdistance then -if not self.bombPlayerResults[_playername]then -self.bombPlayerResults[_playername]={} -end -local _results=self.bombPlayerResults[_playername] -local result={} -result.command=SOCKET.DataType.BOMBRESULT -result.name=_closetTarget.name or"unknown" -result.distance=_distance -result.radial=_closeCoord:HeadingTo(impactcoord) -result.weapon=weapon:GetTypeName()or"unknown" -result.quality=_hitquality -result.player=playerData.playername -result.time=timer.getAbsTime() -result.clock=UTILS.SecondsToClock(result.time,true) -result.midate=UTILS.GetDCSMissionDate() -result.theatre=env.mission.theatre -result.airframe=playerData.airframe -result.roundsFired=0 -result.roundsHit=0 -result.roundsQuality="N/A" -result.rangename=self.rangename -result.attackHdg=attackHdg -result.attackVel=attackVel -result.attackAlt=attackAlt -result.date=os and os.date()or"n/a" -table.insert(_results,result) -self:Impact(result,playerData) -elseif insidezone then -local _message=string.format("%s, weapon impacted too far from nearest range target (>%.1f km). No score!",_callsign,self.scorebombdistance/1000) -if self.useSRS then -local ttstext=string.format("%s, weapon impacted too far from nearest range target, mor than %.1f kilometer. No score!",_callsign,self.scorebombdistance/1000) -self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2) -end -self:_DisplayMessageToGroup(_unit,_message,nil,false) -if self.rangecontrol then -if self.useSRS then -self.controlsrsQ:NewTransmission(_message,nil,self.controlmsrs,nil,1) -else -self.rangecontrol:NewTransmission(RANGE.Sound.RCWeaponImpactedTooFar.filename,RANGE.Sound.RCWeaponImpactedTooFar.duration,self.soundpath,nil,nil,_message,self.subduration) -end -end -else -self:T(self.lid.."Weapon impacted outside range zone.") -end -end -function RANGE:OnEventShot(EventData) -self:F({eventshot=EventData}) -if EventData.Weapon==nil or EventData.IniDCSUnit==nil or EventData.IniPlayerName==nil then -return -end -local weapon=WEAPON:New(EventData.weapon) -local _track=(weapon:IsBomb()and self.trackbombs)or(weapon:IsRocket()and self.trackrockets)or(weapon:IsMissile()and self.trackmissiles) -local _unitName=EventData.IniUnitName -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -local dPR=self.BombtrackThreshold*2 -if _unit and _playername then -dPR=_unit:GetCoordinate():Get2DDistance(self.location) -self:T(self.lid..string.format("Range %s, player %s, player-range distance = %d km.",self.rangename,_playername,dPR/1000)) -end -if _track and dPR<=self.BombtrackThreshold and _unit and _playername then -local playerData=self.PlayerSettings[_playername] -local attackHdg=_unit:GetHeading() -local attackAlt=_unit:GetHeight() -attackAlt=UTILS.MetersToFeet(attackAlt) -local attackVel=_unit:GetVelocityKNOTS() -self:T(self.lid..string.format("RANGE %s: Tracking %s - %s.",self.rangename,weapon:GetTypeName(),weapon:GetName())) -weapon:SetFuncImpact(RANGE._OnImpact,self,playerData,attackHdg,attackAlt,attackVel) -self:T(self.lid..string.format("Range %s, player %s: Tracking of weapon starts in 0.1 seconds.",self.rangename,_playername)) -weapon:StartTrack(0.1) -end -end -function RANGE:onafterStatus(From,Event,To) -if self.verbose>0 then -local fsmstate=self:GetState() -local text=string.format("Range status: %s",fsmstate) -if self.instructor then -local alive="N/A" -if self.instructorrelayname then -local relay=UNIT:FindByName(self.instructorrelayname) -if relay then -alive=tostring(relay:IsAlive()) -end -end -text=text..string.format(", Instructor %.3f MHz (Relay=%s alive=%s)",self.instructorfreq,tostring(self.instructorrelayname),alive) -end -if self.rangecontrol then -local alive="N/A" -if self.rangecontrolrelayname then -local relay=UNIT:FindByName(self.rangecontrolrelayname) -if relay then -alive=tostring(relay:IsAlive()) -end -end -text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)",self.rangecontrolfreq,tostring(self.rangecontrolrelayname),alive) -end -self:I(self.lid..text) -end -self:_CheckPlayers() -self:__Status(-10) -end -function RANGE:onafterEnterRange(From,Event,To,player) -if self.instructor and self.rangecontrol then -if self.useSRS then -local text=string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f MHz",self.rangecontrolfreq) -local ttstext=string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f mega hertz.",self.rangecontrolfreq) -local group=player.client:GetGroup() -self.instructsrsQ:NewTransmission(ttstext,nil,self.instructmsrs,nil,1,{group},text,10) -else -local RF=UTILS.Split(string.format("%.3f",self.rangecontrolfreq),".") -self.instructor:NewTransmission(RANGE.Sound.IREnterRange.filename,RANGE.Sound.IREnterRange.duration,self.soundpath) -self.instructor:Number2Transmission(RF[1]) -if tonumber(RF[2])>0 then -self.instructor:NewTransmission(RANGE.Sound.IRDecimal.filename,RANGE.Sound.IRDecimal.duration,self.soundpath) -self.instructor:Number2Transmission(RF[2]) -end -self.instructor:NewTransmission(RANGE.Sound.IRMegaHertz.filename,RANGE.Sound.IRMegaHertz.duration,self.soundpath) -end -end -end -function RANGE:onafterExitRange(From,Event,To,player) -if self.instructor then -if self.useSRS then -local text="You left the bombing range zone. " -local r=math.random(5) -if r==1 then -text=text.."Have a nice day!" -elseif r==2 then -text=text.."Take care and bye bye!" -elseif r==3 then -text=text.."Talk to you soon!" -elseif r==4 then -text=text.."See you in two weeks!" -elseif r==5 then -text=text.."!" -end -self.instructsrsQ:NewTransmission(text,nil,self.instructmsrs,nil,1,{player.client:GetGroup()},text,10) -else -self.instructor:NewTransmission(RANGE.Sound.IRExitRange.filename,RANGE.Sound.IRExitRange.duration,self.soundpath) -end -end -end -function RANGE:onafterImpact(From,Event,To,result,player) -local targetname=nil -if#self.bombingTargets>1 then -targetname=result.name -end -local text=string.format("%s, impact %03d° for %d ft (%d m)",player.playername,result.radial,UTILS.MetersToFeet(result.distance),result.distance) -if targetname then -text=text..string.format(" from bulls of target %s.",targetname) -else -text=text.."." -end -text=text..string.format(" %s hit.",result.quality) -if self.rangecontrol then -if self.useSRS then -local group=player.client:GetGroup() -self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1,{group},text,10) -else -self.rangecontrol:NewTransmission(RANGE.Sound.RCImpact.filename,RANGE.Sound.RCImpact.duration,self.soundpath,nil,nil,text,self.subduration) -self.rangecontrol:Number2Transmission(string.format("%03d",result.radial),nil,0.1) -self.rangecontrol:NewTransmission(RANGE.Sound.RCDegrees.filename,RANGE.Sound.RCDegrees.duration,self.soundpath) -self.rangecontrol:NewTransmission(RANGE.Sound.RCFor.filename,RANGE.Sound.RCFor.duration,self.soundpath) -self.rangecontrol:Number2Transmission(string.format("%d",UTILS.MetersToFeet(result.distance))) -self.rangecontrol:NewTransmission(RANGE.Sound.RCFeet.filename,RANGE.Sound.RCFeet.duration,self.soundpath) -if result.quality=="POOR"then -self.rangecontrol:NewTransmission(RANGE.Sound.RCPoorHit.filename,RANGE.Sound.RCPoorHit.duration,self.soundpath,nil,0.5) -elseif result.quality=="INEFFECTIVE"then -self.rangecontrol:NewTransmission(RANGE.Sound.RCIneffectiveHit.filename,RANGE.Sound.RCIneffectiveHit.duration,self.soundpath,nil,0.5) -elseif result.quality=="GOOD"then -self.rangecontrol:NewTransmission(RANGE.Sound.RCGoodHit.filename,RANGE.Sound.RCGoodHit.duration,self.soundpath,nil,0.5) -elseif result.quality=="EXCELLENT"then -self.rangecontrol:NewTransmission(RANGE.Sound.RCExcellentHit.filename,RANGE.Sound.RCExcellentHit.duration,self.soundpath,nil,0.5) -end -end -end -if player.unitname and not self.useSRS then -local unit=UNIT:FindByName(player.unitname) -self:_DisplayMessageToGroup(unit,text,nil,true) -self:T(self.lid..text) -end -if self.autosave then -self:Save() -end -if self.funkmanSocket then -self.funkmanSocket:SendTable(result) -end -end -function RANGE:onafterStrafeResult(From,Event,To,player,result) -if self.funkmanSocket then -self.funkmanSocket:SendTable(result) -end -end -function RANGE:onbeforeSave(From,Event,To) -if io and lfs then -return true -else -self:E(self.lid..string.format("WARNING: io and/or lfs not desanitized. Cannot save player results.")) -return false -end -end -function RANGE:onafterSave(From,Event,To) -local function _savefile(filename,data) -local f=io.open(filename,"wb") -if f then -f:write(data) -f:close() -self:I(self.lid..string.format("Saving player results to file %s",tostring(filename))) -else -self:E(self.lid..string.format("ERROR: Could not save results to file %s",tostring(filename))) -end -end -local path=lfs.writedir()..[[Logs\]] -local filename=path..string.format("RANGE-%s_BombingResults.csv",self.rangename) -local scores="Name,Pass,Target,Distance,Radial,Quality,Weapon,Airframe,Mission Time" -for playername,results in pairs(self.bombPlayerResults)do -for i,_result in pairs(results)do -local result=_result -local distance=result.distance -local weapon=result.weapon -local target=result.name -local radial=result.radial -local quality=result.quality -local time=UTILS.SecondsToClock(result.time,true) -local airframe=result.airframe -local date=result.date or"n/a" -scores=scores..string.format("\n%s,%d,%s,%.2f,%03d,%s,%s,%s,%s,%s",playername,i,target,distance,radial,quality,weapon,airframe,time,date) -end -end -_savefile(filename,scores) -end -function RANGE:onbeforeLoad(From,Event,To) -if io and lfs then -return true -else -self:E(self.lid..string.format("WARNING: io and/or lfs not desanitized. Cannot load player results.")) -return false -end -end -function RANGE:onafterLoad(From,Event,To) -local function _loadfile(filename) -local f=io.open(filename,"rb") -if f then -local data=f:read("*all") -f:close() -return data -else -self:E(self.lid..string.format("WARNING: Could not load player results from file %s. File might not exist just yet.",tostring(filename))) -return nil -end -end -local path=lfs.writedir()..[[Logs\]] -local filename=path..string.format("RANGE-%s_BombingResults.csv",self.rangename) -local text=string.format("Loading player bomb results from file %s",filename) -self:I(self.lid..text) -local data=_loadfile(filename) -if data then -local results=UTILS.Split(data,"\n") -table.remove(results,1) -self.bombPlayerResults={} -for _,_result in pairs(results)do -local resultdata=UTILS.Split(_result,",") -local result={} -local playername=resultdata[1] -result.player=playername -result.name=tostring(resultdata[3]) -result.distance=tonumber(resultdata[4]) -result.radial=tonumber(resultdata[5]) -result.quality=tostring(resultdata[6]) -result.weapon=tostring(resultdata[7]) -result.airframe=tostring(resultdata[8]) -result.time=UTILS.ClockToSeconds(resultdata[9]or"00:00:00") -result.date=resultdata[10]or"n/a" -self.bombPlayerResults[playername]=self.bombPlayerResults[playername]or{} -table.insert(self.bombPlayerResults[playername],result) -end -end -end -function RANGE:_SaveTargetSheet(_playername,result) -local function _savefile(filename,data) -local f=io.open(filename,"wb") -if f then -f:write(data) -f:close() -else -env.info("RANGEBOSS EDIT - could not save target sheet to file") -end -end -local path=self.targetpath -if lfs then -path=path or lfs.writedir()..[[Logs\]] -end -local filename=nil -for i=1,9999 do -if self.targetprefix then -filename=string.format("%s_%s-%04d.csv",self.targetprefix,result.airframe,i) -else -local name=UTILS.ReplaceIllegalCharacters(_playername,"_") -filename=string.format("RANGERESULTS-%s_Targetsheet-%s-%04d.csv",self.rangename,name,i) -end -if path~=nil then -filename=path.."\\"..filename -end -local _exists=UTILS.FileExists(filename) -if not _exists then -break -end -end -local data="Name,Target,Rounds Fired,Rounds Hit,Rounds Quality,Airframe,Mission Time,OS Time\n" -local target=result.name -local airframe=result.airframe -local roundsFired=result.roundsFired -local roundsHit=result.roundsHit -local strafeResult=result.roundsQuality -local time=UTILS.SecondsToClock(result.time) -local date="n/a" -if os then -date=os.date() -end -data=data..string.format("%s,%s,%d,%d,%s,%s,%s,%s",_playername,target,roundsFired,roundsHit,strafeResult,airframe,time,date) -_savefile(filename,data) -end -function RANGE._DelayedSmoke(_args) -_args.coord:Smoke(_args.color) -end -function RANGE:_DisplayMyStrafePitResults(_unitName) -self:F(_unitName) -local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local _message=string.format("My Top %d Strafe Pit Results:\n",self.ndisplayresult) -local _results=self.strafePlayerResults[_playername] -if _results==nil then -_message=string.format("%s: No Score yet.",_playername) -else -local _sort=function(a,b) -return a.roundsHit>b.roundsHit -end -table.sort(_results,_sort) -local _bestMsg="" -local _count=1 -for _,_result in pairs(_results)do -local result=_result -_message=_message..string.format("\n[%d] Hits %d - %s - %s",_count,result.roundsHit,result.name,result.roundsQuality) -if _bestMsg==""then -_bestMsg=string.format("Hits %d - %s - %s",result.roundsHit,result.name,result.roundsQuality) -end -if _count==self.ndisplayresult then -break -end -_count=_count+1 -end -_message=_message.."\n\nBEST: ".._bestMsg -end -self:_DisplayMessageToGroup(_unit,_message,nil,true,true,_multiplayer) -end -end -function RANGE:_DisplayStrafePitResults(_unitName) -self:F(_unitName) -local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local _playerResults={} -local _message=string.format("Strafe Pit Results - Top %d Players:\n",self.ndisplayresult) -for _playerName,_results in pairs(self.strafePlayerResults)do -local _best=nil -for _,_result in pairs(_results)do -if _best==nil or _result.roundsHit>_best.roundsHit then -_best=_result -end -end -if _best~=nil then -local text=string.format("%s: Hits %i - %s - %s",_playerName,_best.roundsHit,_best.name,_best.roundsQuality) -table.insert(_playerResults,{msg=text,hits=_best.roundsHit}) -end -end -local _sort=function(a,b) -return a.hits>b.hits -end -table.sort(_playerResults,_sort) -for _i=1,math.min(#_playerResults,self.ndisplayresult)do -_message=_message..string.format("\n[%d] %s",_i,_playerResults[_i].msg) -end -if#_playerResults<1 then -_message=_message.."No player scored yet." -end -self:_DisplayMessageToGroup(_unit,_message,nil,true,true,_multiplayer) -end -end -function RANGE:_DisplayMyBombingResults(_unitName) -self:F(_unitName) -local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local _message=string.format("My Top %d Bombing Results:\n",self.ndisplayresult) -local _results=self.bombPlayerResults[_playername] -if _results==nil then -_message=_playername..": No Score yet." -else -local _sort=function(a,b) -return a.distance180 then -heading=heading-180 -else -heading=heading+180 -end -local mycoord=coord:ToStringA2G(_unit,_settings) -_text=_text..string.format("\n- %s: heading %03d°\n%s",_strafepit.name,heading,mycoord) -end -self:_DisplayMessageToGroup(_unit,_text,nil,true,true,_multiplayer) -end -end -function RANGE:_DisplayRangeWeather(_unitname) -self:F(_unitname) -local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local text="" -local coord=unit:GetCoordinate() -if self.location then -local position=self.location -local T=position:GetTemperature() -local P=position:GetPressure() -local Wd,Ws=position:GetWind() -local Bn,Bd=UTILS.BeaufortScale(Ws) -local WD=string.format('%03d°',Wd) -local Ts=string.format("%d°C",T) -local hPa2inHg=0.0295299830714 -local hPa2mmHg=0.7500615613030 -local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS -local tT=string.format("%d°C",T) -local tW=string.format("%.1f m/s",Ws) -local tP=string.format("%.1f mmHg",P*hPa2mmHg) -if settings:IsImperial()then -tW=string.format("%.1f knots",UTILS.MpsToKnots(Ws)) -tP=string.format("%.2f inHg",P*hPa2inHg) -end -text=text..string.format("Weather Report at %s:\n",self.rangename) -text=text..string.format("--------------------------------------------------\n") -text=text..string.format("Temperature %s\n",tT) -text=text..string.format("Wind from %s at %s (%s)\n",WD,tW,Bd) -text=text..string.format("QFE %.1f hPa = %s",P,tP) -else -text=string.format("No range location defined for range %s.",self.rangename) -end -self:_DisplayMessageToGroup(unit,text,nil,true,true,_multiplayer) -self:T2(self.lid..text) -else -self:T(self.lid..string.format("ERROR! Could not find player unit in RangeInfo! Name = %s",_unitname)) -end -end -function RANGE:_CheckPlayers() -for playername,_playersettings in pairs(self.PlayerSettings)do -local playersettings=_playersettings -local unitname=playersettings.unitname -local unit=UNIT:FindByName(unitname) -if unit and unit:IsAlive()then -if unit:IsInZone(self.rangezone)then -if not playersettings.inzone then -playersettings.inzone=true -self:EnterRange(playersettings) -end -else -if playersettings.inzone==true then -playersettings.inzone=false -self:ExitRange(playersettings) -end -end -end -end -end -function RANGE:_CheckInZone(_unitName) -self:F2(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -local unitheading=0 -if _unit and _playername then -local playerData=self.PlayerSettings[_playername] -local function checkme(targetheading,_zone) -local zone=_zone -local unitheading=_unit:GetHeading() -local pitheading=targetheading-180 -local deltaheading=unitheading-pitheading -local towardspit=math.abs(deltaheading)<=90 or math.abs(deltaheading-360)<=90 -if towardspit then -local vec3=_unit:GetVec3() -local vec2={x=vec3.x,y=vec3.z} -local landheight=land.getHeight(vec2) -local unitalt=vec3.y-landheight -if unitalt<=self.strafemaxalt then -local unitinzone=zone:IsVec2InZone(vec2) -return unitinzone -end -end -return false -end -local _unitID=_unit:GetID() -local _currentStrafeRun=self.strafeStatus[_unitID] -if _currentStrafeRun then -local zone=_currentStrafeRun.zone.polygon -local unitinzone=checkme(_currentStrafeRun.zone.heading,zone) -if unitinzone then -_currentStrafeRun.time=_currentStrafeRun.time+1 -else -_currentStrafeRun.time=_currentStrafeRun.time+1 -if _currentStrafeRun.time<=3 then -self.strafeStatus[_unitID]=nil -local _msg=string.format("%s left strafing zone %s too quickly. No Score.",_playername,_currentStrafeRun.zone.name) -self:_DisplayMessageToGroup(_unit,_msg,nil,true) -if self.rangecontrol then -if self.useSRS then -local group=_unit:GetGroup() -local text="You left the strafing zone too quickly! No score!" -self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1) -else -self.rangecontrol:NewTransmission(RANGE.Sound.RCLeftStrafePitTooQuickly.filename,RANGE.Sound.RCLeftStrafePitTooQuickly.duration,self.soundpath) -end -end -else -local _ammo=self:_GetAmmo(_unitName) -local _result=self.strafeStatus[_unitID] -local _sound=nil -local shots=_result.ammo-_ammo -local accur=0 -if shots>0 then -accur=_result.hits/shots*100 -if accur>100 then -accur=100 -end -end -local resulttext="" -if _result.pastfoulline==true then -resulttext="* INVALID - PASSED FOUL LINE *" -_sound=RANGE.Sound.RCPoorPass -else -if accur>=90 then -resulttext="DEADEYE PASS" -_sound=RANGE.Sound.RCExcellentPass -elseif accur>=75 then -resulttext="EXCELLENT PASS" -_sound=RANGE.Sound.RCExcellentPass -elseif accur>=50 then -resulttext="GOOD PASS" -_sound=RANGE.Sound.RCGoodPass -elseif accur>=25 then -resulttext="INEFFECTIVE PASS" -_sound=RANGE.Sound.RCIneffectivePass -else -resulttext="POOR PASS" -_sound=RANGE.Sound.RCPoorPass -end -end -local _text=string.format("%s, hits on target %s: %d",self:_myname(_unitName),_result.zone.name,_result.hits) -local ttstext=string.format("%s, hits on target %s: %d.",self:_myname(_unitName),_result.zone.name,_result.hits) -if shots and accur then -_text=_text..string.format("\nTotal rounds fired %d. Accuracy %.1f %%.",shots,accur) -ttstext=ttstext..string.format(". Total rounds fired %d. Accuracy %.1f percent.",shots,accur) -end -_text=_text..string.format("\n%s",resulttext) -ttstext=ttstext..string.format(" %s",resulttext) -self:_DisplayMessageToGroup(_unit,_text) -local result={} -result.command=SOCKET.DataType.STRAFERESULT -result.player=_playername -result.name=_result.zone.name or"unknown" -result.time=timer.getAbsTime() -result.clock=UTILS.SecondsToClock(result.time) -result.midate=UTILS.GetDCSMissionDate() -result.theatre=env.mission.theatre -result.roundsFired=shots -result.roundsHit=_result.hits -result.roundsQuality=resulttext -result.strafeAccuracy=accur -result.rangename=self.rangename -result.airframe=playerData.airframe -result.invalid=_result.pastfoulline -self:StrafeResult(playerData,result) -if playerData and playerData.targeton and self.targetsheet then -self:_SaveTargetSheet(_playername,result) -end -if self.rangecontrol then -if self.useSRS then -self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,1) -else -self.rangecontrol:NewTransmission(RANGE.Sound.RCHitsOnTarget.filename,RANGE.Sound.RCHitsOnTarget.duration,self.soundpath) -self.rangecontrol:Number2Transmission(string.format("%d",_result.hits)) -if shots and accur then -self.rangecontrol:NewTransmission(RANGE.Sound.RCTotalRoundsFired.filename,RANGE.Sound.RCTotalRoundsFired.duration,self.soundpath,nil,0.2) -self.rangecontrol:Number2Transmission(string.format("%d",shots),nil,0.2) -self.rangecontrol:NewTransmission(RANGE.Sound.RCAccuracy.filename,RANGE.Sound.RCAccuracy.duration,self.soundpath,nil,0.2) -self.rangecontrol:Number2Transmission(string.format("%d",UTILS.Round(accur,0))) -self.rangecontrol:NewTransmission(RANGE.Sound.RCPercent.filename,RANGE.Sound.RCPercent.duration,self.soundpath) -end -self.rangecontrol:NewTransmission(_sound.filename,_sound.duration,self.soundpath,nil,0.5) -end -end -self.strafeStatus[_unitID]=nil -local _stats=self.strafePlayerResults[_playername]or{} -table.insert(_stats,result) -self.strafePlayerResults[_playername]=_stats -end -end -else -for _,_targetZone in pairs(self.strafeTargets)do -local target=_targetZone -local zone=target.polygon -local unitinzone=checkme(target.heading,zone) -if unitinzone then -local _ammo=self:_GetAmmo(_unitName) -self.strafeStatus[_unitID]={hits=0,zone=target,time=1,ammo=_ammo,pastfoulline=false} -local _msg=string.format("%s, rolling in on strafe pit %s.",self:_myname(_unitName),target.name) -if self.rangecontrol then -if self.useSRS then -self.controlsrsQ:NewTransmission(_msg,nil,self.controlmsrs,nil,1) -else -self.rangecontrol:NewTransmission(RANGE.Sound.RCRollingInOnStrafeTarget.filename,RANGE.Sound.RCRollingInOnStrafeTarget.duration,self.soundpath) -end -end -self:_DisplayMessageToGroup(_unit,_msg,10,true) -self:RollingIn(playerData,target) -break -end -end -end -end -end -function RANGE:_AddF10Commands(_unitName) -self:F(_unitName) -local _unit,playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and playername then -local group=_unit:GetGroup() -local _gid=group:GetID() -if group and _gid then -if not self.MenuAddedTo[_gid]then -self.MenuAddedTo[_gid]=true -local _rangePath=nil -if RANGE.MenuF10Root then -_rangePath=MENU_GROUP:New(group,"On the Range") -else -if RANGE.MenuF10[_gid]==nil then -RANGE.MenuF10[_gid]=MENU_GROUP:New(group,"On the Range") -end -_rangePath=MENU_GROUP:New(group,self.rangename,RANGE.MenuF10[_gid]) -end -local _statsPath=MENU_GROUP:New(group,"Statistics",_rangePath) -local _markPath=MENU_GROUP:New(group,"Mark Targets",_rangePath) -local _settingsPath=MENU_GROUP:New(group,"My Settings",_rangePath) -local _infoPath=MENU_GROUP:New(group,"Range Info",_rangePath) -local _mysmokePath=MENU_GROUP:New(group,"Smoke Color",_settingsPath) -local _myflarePath=MENU_GROUP:New(group,"Flare Color",_settingsPath) -local _MoMap=MENU_GROUP_COMMAND:New(group,"Mark On Map",_markPath,self._MarkTargetsOnMap,self,_unitName) -local _IllRng=MENU_GROUP_COMMAND:New(group,"Illuminate Range",_markPath,self._IlluminateBombTargets,self,_unitName) -local _SSpit=MENU_GROUP_COMMAND:New(group,"Smoke Strafe Pits",_markPath,self._SmokeStrafeTargetBoxes,self,_unitName) -local _SStgts=MENU_GROUP_COMMAND:New(group,"Smoke Strafe Tgts",_markPath,self._SmokeStrafeTargets,self,_unitName) -local _SBtgts=MENU_GROUP_COMMAND:New(group,"Smoke Bomb Tgts",_markPath,self._SmokeBombTargets,self,_unitName) -local _AllSR=MENU_GROUP_COMMAND:New(group,"All Strafe Results",_statsPath,self._DisplayStrafePitResults,self,_unitName) -local _AllBR=MENU_GROUP_COMMAND:New(group,"All Bombing Results",_statsPath,self._DisplayBombingResults,self,_unitName) -local _MySR=MENU_GROUP_COMMAND:New(group,"My Strafe Results",_statsPath,self._DisplayMyStrafePitResults,self,_unitName) -local _MyBR=MENU_GROUP_COMMAND:New(group,"My Bomb Results",_statsPath,self._DisplayMyBombingResults,self,_unitName) -local _ResetST=MENU_GROUP_COMMAND:New(group,"Reset All Stats",_statsPath,self._ResetRangeStats,self,_unitName) -local _BlueSM=MENU_GROUP_COMMAND:New(group,"Blue Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Blue) -local _GrSM=MENU_GROUP_COMMAND:New(group,"Green Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Green) -local _OrSM=MENU_GROUP_COMMAND:New(group,"Orange Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Orange) -local _ReSM=MENU_GROUP_COMMAND:New(group,"Red Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Red) -local _WhSm=MENU_GROUP_COMMAND:New(group,"White Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.White) -local _GrFl=MENU_GROUP_COMMAND:New(group,"Green Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Green) -local _ReFl=MENU_GROUP_COMMAND:New(group,"Red Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Red) -local _WhFl=MENU_GROUP_COMMAND:New(group,"White Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.White) -local _YeFl=MENU_GROUP_COMMAND:New(group,"Yellow Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Yellow) -local _SmDe=MENU_GROUP_COMMAND:New(group,"Smoke Delay On/Off",_settingsPath,self._SmokeBombDelayOnOff,self,_unitName) -local _SmIm=MENU_GROUP_COMMAND:New(group,"Smoke Impact On/Off",_settingsPath,self._SmokeBombImpactOnOff,self,_unitName) -local _FlHi=MENU_GROUP_COMMAND:New(group,"Flare Hits On/Off",_settingsPath,self._FlareDirectHitsOnOff,self,_unitName) -local _AlMeA=MENU_GROUP_COMMAND:New(group,"All Messages On/Off",_settingsPath,self._MessagesToPlayerOnOff,self,_unitName) -local _TrpSh=MENU_GROUP_COMMAND:New(group,"Targetsheet On/Off",_settingsPath,self._TargetsheetOnOff,self,_unitName) -local _WeIn=MENU_GROUP_COMMAND:New(group,"General Info",_infoPath,self._DisplayRangeInfo,self,_unitName) -local _WeRe=MENU_GROUP_COMMAND:New(group,"Weather Report",_infoPath,self._DisplayRangeWeather,self,_unitName) -local _BoTgtgs=MENU_GROUP_COMMAND:New(group,"Bombing Targets",_infoPath,self._DisplayBombTargets,self,_unitName) -local _StrPits=MENU_GROUP_COMMAND:New(group,"Strafe Pits",_infoPath,self._DisplayStrafePits,self,_unitName):Refresh() -end -else -self:E(self.lid.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName or"N/A") -end -else -self:E(self.lid.."Player unit does not exist in AddF10Menu() function. Unit name: ".._unitName or"N/A") -end -end -function RANGE:_GetBombTargetCoordinate(target) -local coord=nil -if target.type==RANGE.TargetType.UNIT then -if target.target and target.target:IsAlive()then -coord=target.target:GetCoordinate() -target.coordinate=coord -else -coord=target.coordinate -end -elseif target.type==RANGE.TargetType.STATIC then -coord=target.coordinate -elseif target.type==RANGE.TargetType.COORD then -coord=target.coordinate -elseif target.type==RANGE.TargetType.SCENERY then -coord=target.coordinate -else -self:E(self.lid.."ERROR: Unknown target type.") -end -return coord -end -function RANGE:_GetAmmo(unitname) -self:F2(unitname) -local ammo=0 -local unit,playername=self:_GetPlayerUnitAndName(unitname) -if unit and playername then -local has_ammo=false -local ammotable=unit:GetAmmo() -self:T2({ammotable=ammotable}) -if ammotable~=nil then -local weapons=#ammotable -self:T2(self.lid..string.format("Number of weapons %d.",weapons)) -for w=1,weapons do -local Nammo=ammotable[w]["count"] -local Tammo=ammotable[w]["desc"]["typeName"] -if string.match(Tammo,"shell")then -ammo=ammo+Nammo -local text=string.format("Player %s has %d rounds ammo of type %s",playername,Nammo,Tammo) -self:T(self.lid..text) -else -local text=string.format("Player %s has %d ammo of type %s",playername,Nammo,Tammo) -self:T(self.lid..text) -end -end -end -end -return ammo -end -function RANGE:_MarkTargetsOnMap(_unitName) -self:F(_unitName) -local group=nil -if _unitName then -group=UNIT:FindByName(_unitName):GetGroup() -end -for _,_bombtarget in pairs(self.bombingTargets)do -local bombtarget=_bombtarget -local coord=self:_GetBombTargetCoordinate(_bombtarget) -if group then -coord:MarkToGroup(string.format("Bomb target %s:\n%s\n%s",bombtarget.name,coord:ToStringLLDMS(),coord:ToStringBULLS(group:GetCoalition())),group) -else -coord:MarkToAll(string.format("Bomb target %s",bombtarget.name)) -end -end -for _,_strafepit in pairs(self.strafeTargets)do -for _,_target in pairs(_strafepit.targets)do -local _target=_target -if _target and _target:IsAlive()then -local coord=_target:GetCoordinate() -if group then -coord:MarkToGroup(string.format("Strafe target %s:\n%s\n%s",_target:GetName(),coord:ToStringLLDMS(),coord:ToStringBULLS(group:GetCoalition())),group) -else -coord:MarkToAll("Strafe target ".._target:GetName()) -end -end -end -end -if _unitName then -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -local text=string.format("%s, %s, range targets are now marked on F10 map.",self.rangename,_playername) -self:_DisplayMessageToGroup(_unit,text,5) -end -end -function RANGE:_IlluminateBombTargets(_unitName) -self:F(_unitName) -local bomb={} -for _,_bombtarget in pairs(self.bombingTargets)do -local _target=_bombtarget.target -local coord=self:_GetBombTargetCoordinate(_bombtarget) -if coord then -table.insert(bomb,coord) -end -end -if#bomb>0 then -local coord=bomb[math.random(#bomb)] -local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z) -c:IlluminationBomb() -end -local strafe={} -for _,_strafepit in pairs(self.strafeTargets)do -for _,_target in pairs(_strafepit.targets)do -local _target=_target -if _target and _target:IsAlive()then -local coord=_target:GetCoordinate() -table.insert(strafe,coord) -end -end -end -if#strafe>0 then -local coord=strafe[math.random(#strafe)] -local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z) -c:IlluminationBomb() -end -if _unitName then -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -local text=string.format("%s, %s, range targets are illuminated.",self.rangename,_playername) -self:_DisplayMessageToGroup(_unit,text,5) -end -end -function RANGE:_ResetRangeStats(_unitName) -self:F(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -self.strafePlayerResults[_playername]=nil -self.bombPlayerResults[_playername]=nil -local text=string.format("%s, %s, your range stats were cleared.",self.rangename,_playername) -self:DisplayMessageToGroup(_unit,text,5,false,true) -end -end -function RANGE:_DisplayMessageToGroup(_unit,_text,_time,_clear,display,_togroup) -self:F({unit=_unit,text=_text,time=_time,clear=_clear}) -_time=_time or self.Tmsg -if _clear==nil or _clear==false then -_clear=false -else -_clear=true -end -if self.messages==false then -return -end -if _unit and _unit:IsAlive()then -local _gid=_unit:GetGroup():GetID() -local _grp=_unit:GetGroup() -local _,playername=self:_GetPlayerUnitAndName(_unit:GetName()) -local playermessage=self.PlayerSettings[playername].messages -if _gid and(playermessage==true or display)and(not self.examinerexclusive)then -if _togroup and _grp then -local m=MESSAGE:New(_text,_time,nil,_clear):ToGroup(_grp) -else -local m=MESSAGE:New(_text,_time,nil,_clear):ToUnit(_unit) -end -end -if self.examinergroupname~=nil then -local _examinerid=GROUP:FindByName(self.examinergroupname) -if _examinerid then -local m=MESSAGE:New(_text,_time,nil,_clear):ToGroup(_examinerid) -end -end -end -end -function RANGE:_SmokeBombImpactOnOff(unitname) -self:F(unitname) -local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname) -if unit and playername then -local text -if self.PlayerSettings[playername].smokebombimpact==true then -self.PlayerSettings[playername].smokebombimpact=false -text=string.format("%s, %s, smoking impact points of bombs is now OFF.",self.rangename,playername) -else -self.PlayerSettings[playername].smokebombimpact=true -text=string.format("%s, %s, smoking impact points of bombs is now ON.",self.rangename,playername) -end -self:_DisplayMessageToGroup(unit,text,5,false,true) -end -end -function RANGE:_SmokeBombDelayOnOff(unitname) -self:F(unitname) -local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname) -if unit and playername then -local text -if self.PlayerSettings[playername].delaysmoke==true then -self.PlayerSettings[playername].delaysmoke=false -text=string.format("%s, %s, delayed smoke of bombs is now OFF.",self.rangename,playername) -else -self.PlayerSettings[playername].delaysmoke=true -text=string.format("%s, %s, delayed smoke of bombs is now ON.",self.rangename,playername) -end -self:_DisplayMessageToGroup(unit,text,5,false,true) -end -end -function RANGE:_MessagesToPlayerOnOff(unitname) -self:F(unitname) -local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname) -if unit and playername then -local text -if self.PlayerSettings[playername].messages==true then -text=string.format("%s, %s, display of ALL messages is now OFF.",self.rangename,playername) -else -text=string.format("%s, %s, display of ALL messages is now ON.",self.rangename,playername) -end -self:_DisplayMessageToGroup(unit,text,5,false,true) -self.PlayerSettings[playername].messages=not self.PlayerSettings[playername].messages -end -end -function RANGE:_TargetsheetOnOff(_unitname) -self:F2(_unitname) -local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local playerData=self.PlayerSettings[playername] -if playerData then -local text="" -if self.targetsheet then -playerData.targeton=not playerData.targeton -if playerData and playerData.targeton==true then -text=string.format("roger, your targetsheets are now SAVED.") -else -text=string.format("affirm, your targetsheets are NOT SAVED.") -end -else -text="negative, target sheet data recorder is broken on this range." -end -self:_DisplayMessageToGroup(unit,text,5,false,false) -end -end -end -function RANGE:_FlareDirectHitsOnOff(unitname) -self:F(unitname) -local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname) -if unit and playername then -local text -if self.PlayerSettings[playername].flaredirecthits==true then -self.PlayerSettings[playername].flaredirecthits=false -text=string.format("%s, %s, flaring direct hits is now OFF.",self.rangename,playername) -else -self.PlayerSettings[playername].flaredirecthits=true -text=string.format("%s, %s, flaring direct hits is now ON.",self.rangename,playername) -end -self:_DisplayMessageToGroup(unit,text,5,false,true) -end -end -function RANGE:_SmokeBombTargets(unitname) -self:F(unitname) -for _,_bombtarget in pairs(self.bombingTargets)do -local _target=_bombtarget.target -local coord=self:_GetBombTargetCoordinate(_bombtarget) -if coord then -coord:Smoke(self.BombSmokeColor) -end -end -if unitname then -local unit,playername=self:_GetPlayerUnitAndName(unitname) -local text=string.format("%s, %s, bombing targets are now marked with %s smoke.",self.rangename,playername,self:_smokecolor2text(self.BombSmokeColor)) -self:_DisplayMessageToGroup(unit,text,5) -end -end -function RANGE:_SmokeStrafeTargets(unitname) -self:F(unitname) -for _,_target in pairs(self.strafeTargets)do -_target.coordinate:Smoke(self.StrafeSmokeColor) -end -if unitname then -local unit,playername=self:_GetPlayerUnitAndName(unitname) -local text=string.format("%s, %s, strafing tragets are now marked with %s smoke.",self.rangename,playername,self:_smokecolor2text(self.StrafeSmokeColor)) -self:_DisplayMessageToGroup(unit,text,5) -end -end -function RANGE:_SmokeStrafeTargetBoxes(unitname) -self:F(unitname) -for _,_target in pairs(self.strafeTargets)do -local zone=_target.polygon -zone:SmokeZone(self.StrafePitSmokeColor,4) -for _,_point in pairs(_target.smokepoints)do -_point:SmokeOrange() -end -end -if unitname then -local unit,playername=self:_GetPlayerUnitAndName(unitname) -local text=string.format("%s, %s, strafing pit approach boxes are now marked with %s smoke.",self.rangename,playername,self:_smokecolor2text(self.StrafePitSmokeColor)) -self:_DisplayMessageToGroup(unit,text,5) -end -end -function RANGE:_playersmokecolor(_unitName,color) -self:F({unitname=_unitName,color=color}) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -self.PlayerSettings[_playername].smokecolor=color -local text=string.format("%s, %s, your bomb impacts are now smoked in %s.",self.rangename,_playername,self:_smokecolor2text(color)) -self:_DisplayMessageToGroup(_unit,text,5) -end -end -function RANGE:_playerflarecolor(_unitName,color) -self:F({unitname=_unitName,color=color}) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -self.PlayerSettings[_playername].flarecolor=color -local text=string.format("%s, %s, your direct hits are now flared in %s.",self.rangename,_playername,self:_flarecolor2text(color)) -self:_DisplayMessageToGroup(_unit,text,5) -end -end -function RANGE:_smokecolor2text(color) -self:F(color) -local txt="" -if color==SMOKECOLOR.Blue then -txt="blue" -elseif color==SMOKECOLOR.Green then -txt="green" -elseif color==SMOKECOLOR.Orange then -txt="orange" -elseif color==SMOKECOLOR.Red then -txt="red" -elseif color==SMOKECOLOR.White then -txt="white" -else -txt=string.format("unknown color (%s)",tostring(color)) -end -return txt -end -function RANGE:_flarecolor2text(color) -self:F(color) -local txt="" -if color==FLARECOLOR.Green then -txt="green" -elseif color==FLARECOLOR.Red then -txt="red" -elseif color==FLARECOLOR.White then -txt="white" -elseif color==FLARECOLOR.Yellow then -txt="yellow" -else -txt=string.format("unknown color (%s)",tostring(color)) -end -return txt -end -function RANGE:_CheckStatic(name) -self:F2(name) -local _DCSstatic=StaticObject.getByName(name) -if _DCSstatic and _DCSstatic:isExist()then -local _MOOSEstatic=STATIC:FindByName(name,false) -if not _MOOSEstatic then -self:T(self.lid..string.format("Adding DCS static to MOOSE database. Name = %s.",name)) -_DATABASE:AddStatic(name) -end -return true -else -self:T3(self.lid..string.format("No static object with name %s exists.",name)) -end -if UNIT:FindByName(name)then -return false -else -self:T3(self.lid..string.format("No unit object with name %s exists.",name)) -end -return nil -end -function RANGE:_GetSpeed(controllable) -self:F2(controllable) -local desc=controllable:GetDesc() -local speed=0 -if desc then -speed=desc.speedMax*3.6 -self:T({speed=speed}) -end -return speed -end -function RANGE:_GetPlayerUnitAndName(_unitName) -self:F2(_unitName) -if _unitName~=nil then -local multiplayer=false -local DCSunit=Unit.getByName(_unitName) -if DCSunit then -local playername=DCSunit:getPlayerName() -local unit=UNIT:Find(DCSunit) -self:T2({DCSunit=DCSunit,unit=unit,playername=playername}) -if DCSunit and unit and playername then -self:F2(playername) -local grp=unit:GetGroup() -if grp and grp:CountAliveUnits()>1 then -multiplayer=true -end -return unit,playername,multiplayer -end -end -end -return nil,nil,nil -end -function RANGE:_myname(unitname) -self:F2(unitname) -local pname="Ghost 1 1" -local unit=UNIT:FindByName(unitname) -if unit and unit:IsAlive()then -local grp=unit:GetGroup() -if grp and grp:IsAlive()then -pname=grp:GetCustomCallSign(true,true) -end -end -return pname -end -do -ZONE_GOAL={ -ClassName="ZONE_GOAL", -Goal=nil, -SmokeTime=nil, -SmokeScheduler=nil, -SmokeColor=nil, -SmokeZone=nil, -} -function ZONE_GOAL:New(Zone) -BASE:I({Zone=Zone}) -local self=BASE:Inherit(self,BASE:New()) -if type(Zone)=="string"then -self=BASE:Inherit(self,ZONE_POLYGON:NewFromGroupName(Zone)) -else -self=BASE:Inherit(self,ZONE_RADIUS:New(Zone:GetName(),Zone:GetVec2(),Zone:GetRadius())) -self:F({Zone=Zone}) -end -self.Goal=GOAL:New() -self.SmokeTime=nil -self:SetSmokeZone(true) -self:AddTransition("*","DestroyedUnit","*") -return self -end -function ZONE_GOAL:GetZone() -return self -end -function ZONE_GOAL:GetZoneName() -return self:GetName() -end -function ZONE_GOAL:SetSmokeZone(switch) -self.SmokeZone=switch -return self -end -function ZONE_GOAL:Smoke(SmokeColor) -self:F({SmokeColor=SmokeColor}) -self.SmokeColor=SmokeColor -end -function ZONE_GOAL:Flare(FlareColor) -self:FlareZone(FlareColor,30) -end -function ZONE_GOAL:onafterGuard() -self:F("Guard") -if self.SmokeZone and not self.SmokeScheduler then -self.SmokeScheduler=self:ScheduleRepeat(1,1,0.1,nil,self.StatusSmoke,self) -end -end -function ZONE_GOAL:StatusSmoke() -self:F({self.SmokeTime,self.SmokeColor}) -if self.SmokeZone then -local CurrentTime=timer.getTime() -if self.SmokeTime==nil or self.SmokeTime+300<=CurrentTime then -if self.SmokeColor then -self:GetCoordinate():Smoke(self.SmokeColor) -self.SmokeTime=CurrentTime -end -end -end -end -function ZONE_GOAL:__Destroyed(EventData) -self:F({"EventDead",EventData}) -self:F({EventData.IniUnit}) -if EventData.IniDCSUnit then -local Vec3=EventData.IniDCSUnit:getPosition().p -self:F({Vec3=Vec3}) -if Vec3 and self:IsVec3InZone(Vec3)then -local PlayerHits=_DATABASE.HITS[EventData.IniUnitName] -if PlayerHits then -for PlayerName,PlayerHit in pairs(PlayerHits.Players or{})do -self.Goal:AddPlayerContribution(PlayerName) -self:DestroyedUnit(EventData.IniUnitName,PlayerName) -end -end -end -end -end -function ZONE_GOAL:MonitorDestroyedUnits() -self:HandleEvent(EVENTS.Dead,self.__Destroyed) -self:HandleEvent(EVENTS.Crash,self.__Destroyed) -end -end -do -ZONE_GOAL_COALITION={ -ClassName="ZONE_GOAL_COALITION", -Coalition=nil, -PreviousCoalition=nil, -UnitCategories=nil, -ObjectCategories=nil, -} -ZONE_GOAL_COALITION.States={} -function ZONE_GOAL_COALITION:New(Zone,Coalition,UnitCategories) -if not Zone then -BASE:E("ERROR: No Zone specified in ZONE_GOAL_COALITION!") -return nil -end -local self=BASE:Inherit(self,ZONE_GOAL:New(Zone)) -self:F({Zone=Zone,Coalition=Coalition}) -self:SetCoalition(Coalition or coalition.side.NEUTRAL) -self:SetUnitCategories(UnitCategories) -self:SetObjectCategories() -return self -end -function ZONE_GOAL_COALITION:SetCoalition(Coalition) -self.PreviousCoalition=self.Coalition or Coalition -self.Coalition=Coalition -return self -end -function ZONE_GOAL_COALITION:SetUnitCategories(UnitCategories) -if UnitCategories and type(UnitCategories)~="table"then -UnitCategories={UnitCategories} -end -self.UnitCategories=UnitCategories or{Unit.Category.GROUND_UNIT} -return self -end -function ZONE_GOAL_COALITION:SetObjectCategories(ObjectCategories) -if ObjectCategories and type(ObjectCategories)~="table"then -ObjectCategories={ObjectCategories} -end -self.ObjectCategories=ObjectCategories or{Object.Category.UNIT,Object.Category.STATIC} -return self -end -function ZONE_GOAL_COALITION:GetCoalition() -return self.Coalition -end -function ZONE_GOAL_COALITION:GetPreviousCoalition() -return self.PreviousCoalition -end -function ZONE_GOAL_COALITION:GetCoalitionName() -return UTILS.GetCoalitionName(self.Coalition) -end -function ZONE_GOAL_COALITION:StatusZone() -local State=self:GetState() -local text=string.format("Zone state=%s, Owner=%s, Scanning...",State,self:GetCoalitionName()) -self:F(text) -self:Scan(self.ObjectCategories,self.UnitCategories) -return self -end -end -do -ZONE_CAPTURE_COALITION={ -ClassName="ZONE_CAPTURE_COALITION", -MarkBlue=nil, -MarkRed=nil, -StartInterval=nil, -RepeatInterval=nil, -HitsOn=nil, -HitTimeLast=nil, -HitTimeAttackOver=nil, -MarkOn=nil, -} -function ZONE_CAPTURE_COALITION:New(Zone,Coalition,UnitCategories,ObjectCategories) -local self=BASE:Inherit(self,ZONE_GOAL_COALITION:New(Zone,Coalition,UnitCategories)) -self:F({Zone=Zone,Coalition=Coalition,UnitCategories=UnitCategories,ObjectCategories=ObjectCategories}) -self:SetObjectCategories(ObjectCategories) -self:SetSmokeZone(false) -self:SetMarkZone(true) -self:SetStartState("Empty") -do -end -do -end -do -end -do -end -self:AddTransition("*","Guard","Guarded") -self:AddTransition("*","Empty","Empty") -self:AddTransition({"Guarded","Empty"},"Attack","Attacked") -self:AddTransition({"Guarded","Attacked","Empty"},"Capture","Captured") -_EVENTDISPATCHER:CreateEventNewZoneGoal(self) -return self -end -function ZONE_CAPTURE_COALITION:Start(StartInterval,RepeatInterval) -self.StartInterval=StartInterval or 1 -self.RepeatInterval=RepeatInterval or 15 -if self.ScheduleStatusZone then -self:ScheduleStop(self.ScheduleStatusZone) -end -self.ScheduleStatusZone=self:ScheduleRepeat(self.StartInterval,self.RepeatInterval,0.1,nil,self.StatusZone,self) -self:HandleEvent(EVENTS.Hit,self.OnEventHit) -self:Mark() -return self -end -function ZONE_CAPTURE_COALITION:Stop() -if self.ScheduleStatusZone then -self:ScheduleStop(self.ScheduleStatusZone) -end -if self.SmokeScheduler then -self:ScheduleStop(self.SmokeScheduler) -end -self:UnHandleEvent(EVENTS.Hit) -end -function ZONE_CAPTURE_COALITION:SetMonitorHits(Switch,TimeAttackOver) -self.HitsOn=Switch -self.HitTimeAttackOver=TimeAttackOver or 5*60 -return self -end -function ZONE_CAPTURE_COALITION:SetMarkZone(Switch) -if Switch==nil or Switch==true then -self.MarkOn=true -else -self.MarkOn=false -end -return self -end -function ZONE_CAPTURE_COALITION:OnEventHit(EventData) -if self.HitsOn then -local UnitHit=EventData.TgtUnit -if UnitHit and UnitHit.ClassName~="SCENERY"then -if UnitHit and UnitHit:IsInZone(self)and UnitHit:GetCoalition()==self.Coalition then -self.HitTimeLast=timer.getTime() -if self:GetState()~="Attacked"then -self:F2("Hit ==> Attack") -self:Attack() -end -end -end -end -end -function ZONE_CAPTURE_COALITION:onafterGuard() -self:F2("After Guard") -if self.SmokeZone and not self.SmokeScheduler then -self.SmokeScheduler=self:ScheduleRepeat(self.StartInterval,self.RepeatInterval,0.1,nil,self.StatusSmoke,self) -end -end -function ZONE_CAPTURE_COALITION:onenterGuarded() -self:F2("Enter Guarded") -self:Mark() -end -function ZONE_CAPTURE_COALITION:onenterCaptured() -self:F2("Enter Captured") -local NewCoalition=self:GetScannedCoalition() -self:F({NewCoalition=NewCoalition}) -self:SetCoalition(NewCoalition) -self:Mark() -self.Goal:Achieved() -end -function ZONE_CAPTURE_COALITION:onenterEmpty() -self:F2("Enter Empty") -self:Mark() -end -function ZONE_CAPTURE_COALITION:onenterAttacked() -self:F2("Enter Attacked") -self:Mark() -end -function ZONE_CAPTURE_COALITION:IsEmpty() -local IsEmpty=self:IsNoneInZone() -self:F({IsEmpty=IsEmpty}) -return IsEmpty -end -function ZONE_CAPTURE_COALITION:IsGuarded() -local IsGuarded=self:IsAllInZoneOfCoalition(self.Coalition) -self:F({IsGuarded=IsGuarded}) -return IsGuarded -end -function ZONE_CAPTURE_COALITION:IsCaptured() -local IsCaptured=self:IsAllInZoneOfOtherCoalition(self.Coalition) -self:F({IsCaptured=IsCaptured}) -return IsCaptured -end -function ZONE_CAPTURE_COALITION:IsAttacked() -local IsAttacked=self:IsSomeInZoneOfCoalition(self.Coalition) -self:F({IsAttacked=IsAttacked}) -return IsAttacked -end -function ZONE_CAPTURE_COALITION:StatusZone() -local State=self:GetState() -self:GetParent(self,ZONE_CAPTURE_COALITION).StatusZone(self) -local Tnow=timer.getTime() -if State~="Guarded"and self:IsGuarded()then -if self.HitTimeLast==nil or Tnow>=self.HitTimeLast+self.HitTimeAttackOver then -self:Guard() -self.HitTimeLast=nil -end -end -if State~="Empty"and self:IsEmpty()then -self:Empty() -end -if State~="Attacked"and self:IsAttacked()then -self:Attack() -end -if State~="Captured"and self:IsCaptured()then -self:Capture() -end -local unitset=self:GetScannedSetUnit() -local nRed=0 -local nBlue=0 -for _,object in pairs(unitset:GetSet())do -local coal=object:GetCoalition() -if object:IsAlive()then -if coal==coalition.side.RED then -nRed=nRed+1 -elseif coal==coalition.side.BLUE then -nBlue=nBlue+1 -end -end -end -if false then -local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s",self:GetZoneName(),self:GetCoalitionName(),UTILS.GetCoalitionName(self:GetPreviousCoalition()),nBlue,nRed,State) -local NewState=self:GetState() -if NewState~=State then -text=text..string.format(" --> %s",NewState) -end -self:I(text) -end -end -function ZONE_CAPTURE_COALITION:Mark() -if self.MarkOn then -local Coord=self:GetCoordinate() -local ZoneName=self:GetZoneName() -local State=self:GetState() -if self.MarkRed then -Coord:RemoveMark(self.MarkRed) -end -if self.MarkBlue then -Coord:RemoveMark(self.MarkBlue) -end -if self.Coalition==coalition.side.BLUE then -self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Blue\nGuard Zone: "..ZoneName.."\nStatus: "..State) -self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Blue\nCapture Zone: "..ZoneName.."\nStatus: "..State) -elseif self.Coalition==coalition.side.RED then -self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Red\nGuard Zone: "..ZoneName.."\nStatus: "..State) -self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Red\nCapture Zone: "..ZoneName.."\nStatus: "..State) -else -self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Neutral\nCapture Zone: "..ZoneName.."\nStatus: "..State) -self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Neutral\nCapture Zone: "..ZoneName.."\nStatus: "..State) -end -end -end -end -ARTY={ -ClassName="ARTY", -lid=nil, -Debug=false, -targets={}, -moves={}, -currentTarget=nil, -currentMove=nil, -Nammo0=0, -Nshells0=0, -Nrockets0=0, -Nmissiles0=0, -Nukes0=0, -Nillu0=0, -Nsmoke0=0, -StatusInterval=10, -WaitForShotTime=300, -DCSdesc=nil, -Type=nil, -DisplayName=nil, -groupname=nil, -alias=nil, -clusters={}, -ismobile=true, -iscargo=false, -cargogroup=nil, -IniGroupStrength=0, -IsArtillery=nil, -RearmingDistance=100, -RearmingGroup=nil, -RearmingGroupSpeed=nil, -RearmingGroupOnRoad=false, -RearmingGroupCoord=nil, -RearmingPlaceCoord=nil, -RearmingArtyOnRoad=false, -InitialCoord=nil, -report=true, -ammoshells={}, -ammorockets={}, -ammomissiles={}, -Nshots=0, -minrange=300, -maxrange=1000000, -nukewarhead=75000, -Nukes=nil, -nukefire=false, -nukefires=nil, -nukerange=nil, -Nillu=nil, -illuPower=1000000, -illuMinalt=500, -illuMaxalt=1000, -Nsmoke=nil, -smokeColor=SMOKECOLOR.Red, -relocateafterfire=false, -relocateRmin=300, -relocateRmax=800, -markallow=false, -markkey=nil, -markreadonly=false, -autorelocate=false, -autorelocatemaxdist=50000, -autorelocateonroad=false, -coalition=nil, -respawnafterdeath=false, -respawndelay=nil -} -ARTY.WeaponType={ -Auto=1073741822, -Cannon=805306368, -Rockets=30720, -CruiseMissile=2097152, -TacticalNukes=666, -IlluminationShells=667, -SmokeShells=668, -} -ARTY.db={ -["2B11 mortar"]={ -minrange=500, -maxrange=7000, -reloadtime=30, -}, -["SPH 2S1 Gvozdika"]={ -minrange=300, -maxrange=15000, -reloadtime=nil, -}, -["SPH 2S19 Msta"]={ -minrange=300, -maxrange=23500, -reloadtime=nil, -}, -["SPH 2S3 Akatsia"]={ -minrange=300, -maxrange=17000, -reloadtime=nil, -}, -["SPH 2S9 Nona"]={ -minrange=500, -maxrange=7000, -reloadtime=nil, -}, -["SPH M109 Paladin"]={ -minrange=300, -maxrange=22000, -reloadtime=nil, -}, -["SpGH Dana"]={ -minrange=300, -maxrange=18700, -reloadtime=nil, -}, -["MLRS BM-21 Grad"]={ -minrange=5000, -maxrange=19000, -reloadtime=420, -}, -["MLRS 9K57 Uragan BM-27"]={ -minrange=11500, -maxrange=35800, -reloadtime=840, -}, -["MLRS 9A52 Smerch"]={ -minrange=20000, -maxrange=70000, -reloadtime=2160, -}, -["MLRS M270"]={ -minrange=10000, -maxrange=32000, -reloadtime=540, -}, -} -ARTY.version="1.3.0" -function ARTY:New(group,alias) -local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) -if type(group)=="string"then -self.groupname=group -group=GROUP:FindByName(group) -if not group then -self:E(string.format("ERROR: Requested ARTY group %s does not exist! (Has to be a MOOSE group.)",self.groupname)) -return nil -end -end -if group then -self:T(string.format("ARTY script version %s. Added group %s.",ARTY.version,group:GetName())) -else -self:E("ERROR: Requested ARTY group does not exist! (Has to be a MOOSE group.)") -return nil -end -if not(group:IsGround()or group:IsShip())then -self:E(string.format("ERROR: ARTY group %s has to be a GROUND or SHIP group!",group:GetName())) -return nil -end -self:SetControllable(group) -self.groupname=group:GetName() -self.coalition=group:GetCoalition() -if alias~=nil then -self.alias=tostring(alias) -else -self.alias=self.groupname -end -self.lid=string.format("ARTY %s | ",self.alias) -self.InitialCoord=group:GetCoordinate() -local DCSgroup=Group.getByName(group:GetName()) -local DCSunit=DCSgroup:getUnit(1) -self.DCSdesc=DCSunit:getDesc() -self:T3(self.lid.."DCS descriptors for group "..group:GetName()) -for id,desc in pairs(self.DCSdesc)do -self:T3({id=id,desc=desc}) -end -self.SpeedMax=group:GetSpeedMax() -if self.SpeedMax>1 then -self.ismobile=true -else -self.ismobile=false -end -self.dtTrack=0.2 -self.Speed=self.SpeedMax*0.7 -self.DisplayName=self.DCSdesc.displayName -self.IsArtillery=DCSunit:hasAttribute("Artillery") -self.Type=group:GetTypeName() -self.IniGroupStrength=#group:GetUnits() -self:AddTransition("*","Start","CombatReady") -self:AddTransition("CombatReady","OpenFire","Firing") -self:AddTransition("Firing","CeaseFire","CombatReady") -self:AddTransition("CombatReady","Winchester","OutOfAmmo") -self:AddTransition({"CombatReady","OutOfAmmo"},"Rearm","Rearming") -self:AddTransition("Rearming","Rearmed","Rearmed") -self:AddTransition("*","Move","Moving") -self:AddTransition("Moving","Arrived","Arrived") -self:AddTransition("*","NewTarget","*") -self:AddTransition("*","CombatReady","CombatReady") -self:AddTransition("*","Status","*") -self:AddTransition("*","NewMove","*") -self:AddTransition("*","Dead","*") -self:AddTransition("*","Respawn","CombatReady") -self:AddTransition("*","Loaded","InTransit") -self:AddTransition("InTransit","UnLoaded","CombatReady") -self:AddTransition("Rearming","Arrived","Rearming") -self:AddTransition("Rearming","Move","Rearming") -return self -end -function ARTY:NewFromCargoGroup(cargogroup,alias) -if cargogroup then -BASE:T(string.format("ARTY script version %s. Added CARGO group %s.",ARTY.version,cargogroup:GetName())) -else -BASE:E("ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)") -return nil -end -local group=cargogroup:GetObject() -local arty=ARTY:New(group,alias) -arty.iscargo=true -arty.cargogroup=cargogroup -return arty -end -function ARTY:AssignTargetCoord(coord,prio,radius,nshells,maxengage,time,weapontype,name,unique) -self:F({coord=coord,prio=prio,radius=radius,nshells=nshells,maxengage=maxengage,time=time,weapontype=weapontype,name=name,unique=unique}) -nshells=nshells or 5 -radius=radius or 100 -maxengage=maxengage or 1 -prio=prio or 50 -prio=math.max(1,prio) -prio=math.min(100,prio) -if unique==nil then -unique=false -end -weapontype=weapontype or ARTY.WeaponType.Auto -local text=nil -if coord:IsInstanceOf("GROUP")then -text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a GROUP. Converting to COORDINATE..." -coord=coord:GetCoordinate() -elseif coord:IsInstanceOf("UNIT")then -text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a UNIT. Converting to COORDINATE..." -coord=coord:GetCoordinate() -elseif coord:IsInstanceOf("POSITIONABLE")then -text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a POSITIONABLE. Converting to COORDINATE..." -coord=coord:GetCoordinate() -elseif coord:IsInstanceOf("COORDINATE")then -else -text="ERROR: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter!" -MESSAGE:New(text,30):ToAll() -self:E(self.lid..text) -return nil -end -if text~=nil then -self:E(self.lid..text) -end -local _name=name or coord:ToStringLLDMS() -local _unique=true -_name,_unique=self:_CheckName(self.targets,_name,not unique) -if unique==true and _unique==false then -self:T(self.lid..string.format("%s: target %s should have a unique name but name was already given. Rejecting target!",self.groupname,_name)) -return nil -end -local _time -if type(time)=="string"then -_time=self:_ClockToSeconds(time) -elseif type(time)=="number"then -_time=timer.getAbsTime()+time -else -_time=timer.getAbsTime() -end -local _target={name=_name,coord=coord,radius=radius,nshells=nshells,engaged=0,underfire=false,prio=prio,maxengage=maxengage,time=_time,weapontype=weapontype} -table.insert(self.targets,_target) -self:__NewTarget(1,_target) -return _name -end -function ARTY:AssignAttackGroup(group,prio,radius,nshells,maxengage,time,weapontype,name,unique) -nshells=nshells or 5 -radius=radius or 100 -maxengage=maxengage or 1 -prio=prio or 50 -prio=math.max(1,prio) -prio=math.min(100,prio) -if unique==nil then -unique=false -end -weapontype=weapontype or ARTY.WeaponType.Auto -if type(group)=="string"then -group=GROUP:FindByName(group) -end -if group and group:IsAlive()then -local coord=group:GetCoordinate() -local _name=group:GetName() -local _unique=true -_name,_unique=self:_CheckName(self.targets,_name,not unique) -if unique==true and _unique==false then -self:T(self.lid..string.format("%s: target %s should have a unique name but name was already given. Rejecting target!",self.groupname,_name)) -return nil -end -local _time -if type(time)=="string"then -_time=self:_ClockToSeconds(time) -elseif type(time)=="number"then -_time=timer.getAbsTime()+time -else -_time=timer.getAbsTime() -end -local target={} -target.attackgroup=true -target.name=_name -target.coord=coord -target.radius=radius -target.nshells=nshells -target.engaged=0 -target.underfire=false -target.prio=prio -target.time=_time -target.maxengage=maxengage -target.weapontype=weapontype -table.insert(self.targets,target) -self:__NewTarget(1,target) -return _name -else -self:E("ERROR: Group does not exist!") -end -return nil -end -function ARTY:AssignMoveCoord(coord,time,speed,onroad,cancel,name,unique) -self:F({coord=coord,time=time,speed=speed,onroad=onroad,cancel=cancel,name=name,unique=unique}) -if not self.ismobile then -self:T(self.lid..string.format("%s: group is immobile. Rejecting move request!",self.groupname)) -return nil -end -if unique==nil then -unique=false -end -local _name=name or coord:ToStringLLDMS() -local _unique=true -_name,_unique=self:_CheckName(self.moves,_name,not unique) -if unique==true and _unique==false then -self:T(self.lid..string.format("%s: move %s should have a unique name but name was already given. Rejecting move!",self.groupname,_name)) -return nil -end -if speed then -speed=math.min(speed,self.SpeedMax) -elseif self.Speed then -speed=self.Speed -else -speed=self.SpeedMax*0.7 -end -if onroad==nil then -onroad=false -end -if cancel==nil then -cancel=false -end -local _time -if type(time)=="string"then -_time=self:_ClockToSeconds(time) -elseif type(time)=="number"then -_time=timer.getAbsTime()+time -else -_time=timer.getAbsTime() -end -local _move={name=_name,coord=coord,time=_time,speed=speed,onroad=onroad,cancel=cancel} -table.insert(self.moves,_move) -return _name -end -function ARTY:SetAlias(alias) -self:F({alias=alias}) -self.alias=tostring(alias) -return self -end -function ARTY:AddToCluster(clusters) -self:F({clusters=clusters}) -local names -if type(clusters)=="table"then -names=clusters -elseif type(clusters)=="string"then -names={clusters} -else -self:E(self.lid.."ERROR: Input parameter must be a string or a table in ARTY:AddToCluster()!") -return -end -for _,cluster in pairs(names)do -table.insert(self.clusters,cluster) -end -return self -end -function ARTY:SetMinFiringRange(range) -self:F({range=range}) -self.minrange=range*1000 or 100 -return self -end -function ARTY:SetMaxFiringRange(range) -self:F({range=range}) -self.maxrange=range*1000 or 1000*1000 -return self -end -function ARTY:SetStatusInterval(interval) -self:F({interval=interval}) -self.StatusInterval=interval or 10 -return self -end -function ARTY:SetTrackInterval(interval) -self.dtTrack=interval or 0.2 -return self -end -function ARTY:SetWaitForShotTime(waittime) -self:F({waittime=waittime}) -self.WaitForShotTime=waittime or 300 -return self -end -function ARTY:SetRearmingDistance(distance) -self:F({distance=distance}) -self.RearmingDistance=distance or 100 -return self -end -function ARTY:SetRearmingGroup(group) -self:F({group=group}) -self.RearmingGroup=group -return self -end -function ARTY:SetRearmingGroupSpeed(speed) -self:F({speed=speed}) -self.RearmingGroupSpeed=speed -return self -end -function ARTY:SetRearmingGroupOnRoad(onroad) -self:F({onroad=onroad}) -if onroad==nil then -onroad=true -end -self.RearmingGroupOnRoad=onroad -return self -end -function ARTY:SetRearmingArtyOnRoad(onroad) -self:F({onroad=onroad}) -if onroad==nil then -onroad=true -end -self.RearmingArtyOnRoad=onroad -return self -end -function ARTY:SetRearmingPlace(coord) -self:F({coord=coord}) -self.RearmingPlaceCoord=coord -return self -end -function ARTY:SetAutoRelocateToFiringRange(maxdistance,onroad) -self:F({distance=maxdistance,onroad=onroad}) -self.autorelocate=true -self.autorelocatemaxdist=maxdistance or 50 -self.autorelocatemaxdist=self.autorelocatemaxdist*1000 -if onroad==nil then -onroad=false -end -self.autorelocateonroad=onroad -return self -end -function ARTY:SetAutoRelocateAfterEngagement(rmax,rmin) -self.relocateafterfire=true -self.relocateRmax=rmax or 800 -self.relocateRmin=rmin or 300 -self.relocateRmin=math.min(self.relocateRmin,self.relocateRmax) -return self -end -function ARTY:SetReportON() -self.report=true -return self -end -function ARTY:SetReportOFF() -self.report=false -return self -end -function ARTY:SetRespawnOnDeath(delay) -self.respawnafterdeath=true -self.respawndelay=delay -return self -end -function ARTY:SetDebugON() -self.Debug=true -return self -end -function ARTY:SetDebugOFF() -self.Debug=false -return self -end -function ARTY:SetSpeed(speed) -self.Speed=speed -return self -end -function ARTY:RemoveTarget(name) -self:F2(name) -local id=self:_GetTargetIndexByName(name) -if id then -self:T(self.lid..string.format("Group %s: Removing target %s (id=%d).",self.groupname,name,id)) -table.remove(self.targets,id) -if self.markallow then -local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name) -if batteryname==self.groupname and markTargetID~=nil then -COORDINATE:RemoveMark(markTargetID) -end -end -end -self:T(self.lid..string.format("Group %s: Number of targets = %d.",self.groupname,#self.targets)) -end -function ARTY:RemoveMove(name) -self:F2(name) -local id=self:_GetMoveIndexByName(name) -if id then -self:T(self.lid..string.format("Group %s: Removing move %s (id=%d).",self.groupname,name,id)) -table.remove(self.moves,id) -if self.markallow then -local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name) -if batteryname==self.groupname and markMoveID~=nil then -COORDINATE:RemoveMark(markMoveID) -end -end -end -self:T(self.lid..string.format("Group %s: Number of moves = %d.",self.groupname,#self.moves)) -end -function ARTY:RemoveAllTargets() -self:F2() -for _,target in pairs(self.targets)do -self:RemoveTarget(target.name) -end -end -function ARTY:SetShellTypes(tableofnames) -self:F2(tableofnames) -self.ammoshells={} -for _,_type in pairs(tableofnames)do -table.insert(self.ammoshells,_type) -end -return self -end -function ARTY:SetRocketTypes(tableofnames) -self:F2(tableofnames) -self.ammorockets={} -for _,_type in pairs(tableofnames)do -table.insert(self.ammorockets,_type) -end -return self -end -function ARTY:SetMissileTypes(tableofnames) -self:F2(tableofnames) -self.ammomissiles={} -for _,_type in pairs(tableofnames)do -table.insert(self.ammomissiles,_type) -end -return self -end -function ARTY:SetTacNukeShells(n) -self.Nukes=n -return self -end -function ARTY:SetTacNukeWarhead(strength) -self.nukewarhead=strength or 0.075 -self.nukewarhead=self.nukewarhead*1000*1000 -return self -end -function ARTY:SetIlluminationShells(n,power) -self.Nillu=n -self.illuPower=power or 1.0 -self.illuPower=self.illuPower*1000000 -return self -end -function ARTY:SetIlluminationMinMaxAlt(minalt,maxalt) -self.illuMinalt=minalt or 500 -self.illuMaxalt=maxalt or 1000 -if self.illuMinalt>self.illuMaxalt then -self.illuMinalt=self.illuMaxalt -end -return self -end -function ARTY:SetSmokeShells(n,color) -self.Nsmoke=n -self.smokeColor=color or SMOKECOLOR.Red -return self -end -function ARTY:SetTacNukeFires(nfires,range) -self.nukefire=true -self.nukefires=nfires -self.nukerange=range -return self -end -function ARTY:SetMarkAssignmentsOn(key,readonly) -self.markkey=key -self.markallow=true -if readonly==nil then -self.markreadonly=false -end -return self -end -function ARTY:SetMarkTargetsOff() -self.markallow=false -self.markkey=nil -return self -end -function ARTY:onafterStart(Controllable,From,Event,To) -self:_EventFromTo("onafterStart",Event,From,To) -local text=string.format("Started ARTY version %s for group %s.",ARTY.version,Controllable:GetName()) -self:I(self.lid..text) -MESSAGE:New(text,5):ToAllIf(self.Debug) -self.Nammo0,self.Nshells0,self.Nrockets0,self.Nmissiles0=self:GetAmmo(self.Debug) -if self.nukerange==nil then -self.nukerange=1500/75000*self.nukewarhead -end -if self.nukefires==nil then -self.nukefires=20/1000/1000*self.nukerange*self.nukerange -end -if self.Nukes~=nil then -self.Nukes0=math.min(self.Nukes,self.Nshells0) -else -self.Nukes=0 -self.Nukes0=0 -end -if self.Nillu~=nil then -self.Nillu0=math.min(self.Nillu,self.Nshells0) -else -self.Nillu=0 -self.Nillu0=0 -end -if self.Nsmoke~=nil then -self.Nsmoke0=math.min(self.Nsmoke,self.Nshells0) -else -self.Nsmoke=0 -self.Nsmoke0=0 -end -local _dbproperties=self:_CheckDB(self.DisplayName) -self:T({dbproperties=_dbproperties}) -if _dbproperties~=nil then -for property,value in pairs(_dbproperties)do -self:T({property=property,value=value}) -self[property]=value -end -end -if not self.ismobile then -self.RearmingPlaceCoord=nil -self.relocateafterfire=false -self.autorelocate=false -end -self.Speed=math.min(self.Speed,self.SpeedMax) -if self.RearmingGroup then -local speedmax=self.RearmingGroup:GetSpeedMax() -self:T(self.lid..string.format("%s, rearming group %s max speed = %.1f km/h.",self.groupname,self.RearmingGroup:GetName(),speedmax)) -if self.RearmingGroupSpeed==nil then -self.RearmingGroupSpeed=speedmax*0.5 -else -self.RearmingGroupSpeed=math.min(self.RearmingGroupSpeed,self.RearmingGroup:GetSpeedMax()) -end -else -self.RearmingGroupSpeed=23 -end -local text=string.format("\n******************************************************\n") -text=text..string.format("Arty group = %s\n",self.groupname) -text=text..string.format("Arty alias = %s\n",self.alias) -text=text..string.format("Artillery attribute = %s\n",tostring(self.IsArtillery)) -text=text..string.format("Type = %s\n",self.Type) -text=text..string.format("Display Name = %s\n",self.DisplayName) -text=text..string.format("Number of units = %d\n",self.IniGroupStrength) -text=text..string.format("Speed max = %d km/h\n",self.SpeedMax) -text=text..string.format("Speed default = %d km/h\n",self.Speed) -text=text..string.format("Is mobile = %s\n",tostring(self.ismobile)) -text=text..string.format("Is cargo = %s\n",tostring(self.iscargo)) -text=text..string.format("Min range = %.1f km\n",self.minrange/1000) -text=text..string.format("Max range = %.1f km\n",self.maxrange/1000) -text=text..string.format("Total ammo count = %d\n",self.Nammo0) -text=text..string.format("Number of shells = %d\n",self.Nshells0) -text=text..string.format("Number of rockets = %d\n",self.Nrockets0) -text=text..string.format("Number of missiles = %d\n",self.Nmissiles0) -text=text..string.format("Number of nukes = %d\n",self.Nukes0) -text=text..string.format("Nuclear warhead = %d tons TNT\n",self.nukewarhead/1000) -text=text..string.format("Nuclear demolition = %d m\n",self.nukerange) -text=text..string.format("Nuclear fires = %d (active=%s)\n",self.nukefires,tostring(self.nukefire)) -text=text..string.format("Number of illum. = %d\n",self.Nillu0) -text=text..string.format("Illuminaton Power = %.3f mcd\n",self.illuPower/1000000) -text=text..string.format("Illuminaton Minalt = %d m\n",self.illuMinalt) -text=text..string.format("Illuminaton Maxalt = %d m\n",self.illuMaxalt) -text=text..string.format("Number of smoke = %d\n",self.Nsmoke0) -text=text..string.format("Smoke color = %d\n",self.smokeColor) -if self.RearmingGroup or self.RearmingPlaceCoord then -text=text..string.format("Rearming safe dist. = %d m\n",self.RearmingDistance) -end -if self.RearmingGroup then -text=text..string.format("Rearming group = %s\n",self.RearmingGroup:GetName()) -text=text..string.format("Rearming group speed= %d km/h\n",self.RearmingGroupSpeed) -text=text..string.format("Rearming group roads= %s\n",tostring(self.RearmingGroupOnRoad)) -end -if self.RearmingPlaceCoord then -local dist=self.InitialCoord:Get2DDistance(self.RearmingPlaceCoord) -text=text..string.format("Rearming coord dist = %d m\n",dist) -text=text..string.format("Rearming ARTY roads = %s\n",tostring(self.RearmingArtyOnRoad)) -end -text=text..string.format("Relocate after fire = %s\n",tostring(self.relocateafterfire)) -text=text..string.format("Relocate min dist. = %d m\n",self.relocateRmin) -text=text..string.format("Relocate max dist. = %d m\n",self.relocateRmax) -text=text..string.format("Auto move in range = %s\n",tostring(self.autorelocate)) -text=text..string.format("Auto move dist. max = %.1f km\n",self.autorelocatemaxdist/1000) -text=text..string.format("Auto move on road = %s\n",tostring(self.autorelocateonroad)) -text=text..string.format("Marker assignments = %s\n",tostring(self.markallow)) -text=text..string.format("Marker auth. key = %s\n",tostring(self.markkey)) -text=text..string.format("Marker readonly = %s\n",tostring(self.markreadonly)) -text=text..string.format("Clusters:\n") -for _,cluster in pairs(self.clusters)do -text=text..string.format("- %s\n",tostring(cluster)) -end -text=text..string.format("******************************************************\n") -text=text..string.format("Targets:\n") -for _,target in pairs(self.targets)do -text=text..string.format("- %s\n",self:_TargetInfo(target)) -local possible=self:_CheckWeaponTypePossible(target) -if not possible then -self:E(self.lid..string.format("WARNING: Selected weapon type %s is not possible",self:_WeaponTypeName(target.weapontype))) -end -if self.Debug then -local zone=ZONE_RADIUS:New(target.name,target.coord:GetVec2(),target.radius) -zone:BoundZone(180,coalition.side.NEUTRAL) -end -end -text=text..string.format("Moves:\n") -for i=1,#self.moves do -text=text..string.format("- %s\n",self:_MoveInfo(self.moves[i])) -end -text=text..string.format("******************************************************\n") -text=text..string.format("Shell types:\n") -for _,_type in pairs(self.ammoshells)do -text=text..string.format("- %s\n",_type) -end -text=text..string.format("Rocket types:\n") -for _,_type in pairs(self.ammorockets)do -text=text..string.format("- %s\n",_type) -end -text=text..string.format("Missile types:\n") -for _,_type in pairs(self.ammomissiles)do -text=text..string.format("- %s\n",_type) -end -text=text..string.format("******************************************************") -if self.Debug then -self:I(self.lid..text) -else -self:T(self.lid..text) -end -self.Controllable:OptionROEHoldFire() -self:HandleEvent(EVENTS.Shot) -self:HandleEvent(EVENTS.Dead) -if self.markallow then -world.addEventHandler(self) -end -self:__Status(self.StatusInterval) -end -function ARTY:_CheckDB(displayname) -for _type,_properties in pairs(ARTY.db)do -self:T({type=_type,properties=_properties}) -if _type==displayname then -self:T({type=_type,properties=_properties}) -return _properties -end -end -return nil -end -function ARTY:_StatusReport(display) -if display==nil then -display=false -end -local Nammo,Nshells,Nrockets,Nmissiles=self:GetAmmo() -local Nnukes=self.Nukes -local Nillu=self.Nillu -local Nsmoke=self.Nsmoke -local Tnow=timer.getTime() -local Clock=self:_SecondsToClock(timer.getAbsTime()) -local text=string.format("\n******************* STATUS ***************************\n") -text=text..string.format("ARTY group = %s\n",self.groupname) -text=text..string.format("Clock = %s\n",Clock) -text=text..string.format("FSM state = %s\n",self:GetState()) -text=text..string.format("Total ammo count = %d\n",Nammo) -text=text..string.format("Number of shells = %d\n",Nshells) -text=text..string.format("Number of rockets = %d\n",Nrockets) -text=text..string.format("Number of missiles = %d\n",Nmissiles) -text=text..string.format("Number of nukes = %d\n",Nnukes) -text=text..string.format("Number of illum. = %d\n",Nillu) -text=text..string.format("Number of smoke = %d\n",Nsmoke) -if self.currentTarget then -text=text..string.format("Current Target = %s\n",tostring(self.currentTarget.name)) -text=text..string.format("Curr. Tgt assigned = %d\n",Tnow-self.currentTarget.Tassigned) -else -text=text..string.format("Current Target = %s\n","none") -end -text=text..string.format("Nshots curr. Target = %d\n",self.Nshots) -text=text..string.format("Targets:\n") -for i=1,#self.targets do -text=text..string.format("- %s\n",self:_TargetInfo(self.targets[i])) -end -if self.currentMove then -text=text..string.format("Current Move = %s\n",tostring(self.currentMove.name)) -else -text=text..string.format("Current Move = %s\n","none") -end -text=text..string.format("Moves:\n") -for i=1,#self.moves do -text=text..string.format("- %s\n",self:_MoveInfo(self.moves[i])) -end -text=text..string.format("******************************************************") -env.info(self.lid..text) -MESSAGE:New(text,20):Clear():ToCoalitionIf(self.coalition,display) -end -function ARTY._FuncTrack(weapon,self,target) -local _coord=weapon.coordinate -local _dist=_coord:Get2DDistance(target.coord) -local _destroyweapon=false -self:T3(self.lid..string.format("ARTY %s weapon to target dist = %d m",self.groupname,_dist)) -if target.weapontype==ARTY.WeaponType.IlluminationShells then -if _dist0 -local _trackillu=self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu>0 -local _tracksmoke=self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke>0 -if _tracknuke or _trackillu or _tracksmoke then -self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.",self.groupname)) -local weapon=WEAPON:New(EventData.weapon) -weapon:SetTimeStepTrack(self.dtTrack) -local target=UTILS.DeepCopy(self.currentTarget) -weapon:SetFuncTrack(ARTY._FuncTrack,self,target) -weapon:SetFuncImpact(ARTY._FuncImpact,self,target) -weapon:StartTrack(2) -end -local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo() -if self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes then -self.Nukes=self.Nukes-1 -end -if self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells then -self.Nillu=self.Nillu-1 -end -if self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells then -self.Nsmoke=self.Nsmoke-1 -end -local _outofammo=false -if _nammo==0 then -self:T(self.lid..string.format("Group %s completely out of ammo.",self.groupname)) -_outofammo=true -end -local _partlyoutofammo=self:_CheckOutOfAmmo({self.currentTarget}) -local _weapontype=self:_WeaponTypeName(self.currentTarget.weapontype) -self:T(self.lid..string.format("Group %s ammo: total=%d, shells=%d, rockets=%d, missiles=%d",self.groupname,_nammo,_nshells,_nrockets,_nmissiles)) -self:T(self.lid..string.format("Group %s uses weapontype %s for current target.",self.groupname,_weapontype)) -local _ceasefire=false -local _relocate=false -if self.Nshots>=self.currentTarget.nshells then -local text=string.format("Group %s stop firing on target %s.",self.groupname,self.currentTarget.name) -self:T(self.lid..text) -MESSAGE:New(text,5):ToAllIf(self.Debug) -_ceasefire=true -_relocate=self.relocateafterfire -end -if _outofammo or _partlyoutofammo then -_ceasefire=true -end -if _relocate then -self:_Relocate() -end -if _ceasefire then -self:CeaseFire(self.currentTarget) -end -else -self:E(self.lid..string.format("WARNING: No current target for group %s?!",self.groupname)) -end -end -end -end -function ARTY:onEvent(Event) -if Event==nil or Event.idx==nil then -self:T3("Skipping onEvent. Event or Event.idx unknown.") -return true -end -self:T2(string.format("Event captured = %s",tostring(self.groupname))) -self:T2(string.format("Event id = %s",tostring(Event.id))) -self:T2(string.format("Event time = %s",tostring(Event.time))) -self:T2(string.format("Event idx = %s",tostring(Event.idx))) -self:T2(string.format("Event coalition = %s",tostring(Event.coalition))) -self:T2(string.format("Event group id = %s",tostring(Event.groupID))) -self:T2(string.format("Event text = %s",tostring(Event.text))) -if Event.initiator~=nil then -local _unitname=Event.initiator:getName() -self:T2(string.format("Event ini unit name = %s",tostring(_unitname))) -end -if Event.id==world.event.S_EVENT_MARK_ADDED then -self:T2({event="S_EVENT_MARK_ADDED",battery=self.groupname,vec3=Event.pos}) -elseif Event.id==world.event.S_EVENT_MARK_CHANGE then -self:T({event="S_EVENT_MARK_CHANGE",battery=self.groupname,vec3=Event.pos}) -self:_OnEventMarkChange(Event) -elseif Event.id==world.event.S_EVENT_MARK_REMOVED then -self:T2({event="S_EVENT_MARK_REMOVED",battery=self.groupname,vec3=Event.pos}) -self:_OnEventMarkRemove(Event) -end -end -function ARTY:_OnEventMarkRemove(Event) -local batterycoalition=self.coalition -if Event.text~=nil and Event.text:find("BATTERY")then -local _cancelmove=false -local _canceltarget=false -local _name="" -local _id=nil -if Event.text:find("Marked Relocation")then -_cancelmove=true -_name=self:_MarkMoveName(Event.idx) -_id=self:_GetMoveIndexByName(_name) -elseif Event.text:find("Marked Target")then -_canceltarget=true -_name=self:_MarkTargetName(Event.idx) -_id=self:_GetTargetIndexByName(_name) -else -return -end -if _id==nil then -return -end -if(batterycoalition==Event.coalition and self.markkey==nil)or self.markkey~=nil then -local _validkey=self:_MarkerKeyAuthentification(Event.text) -if _validkey then -if _cancelmove then -if self.currentMove and self.currentMove.name==_name then -self.Controllable:ClearTasks() -self:Arrived() -else -self:RemoveMove(_name) -end -elseif _canceltarget then -if self.currentTarget and self.currentTarget.name==_name then -self:CeaseFire(self.currentTarget) -self:RemoveTarget(_name) -else -self:RemoveTarget(_name) -end -end -end -end -end -end -function ARTY:_OnEventMarkChange(Event) -if Event.text~=nil and Event.text:lower():find("arty")then -local vec3={y=Event.pos.y,x=Event.pos.x,z=Event.pos.z} -local _coord=COORDINATE:NewFromVec3(vec3) -_coord.y=_coord:GetLandHeight() -local batterycoalition=self.coalition -local batteryname=self.groupname -if(batterycoalition==Event.coalition and self.markkey==nil)or self.markkey~=nil then -local _assign=self:_Markertext(Event.text) -if _assign==nil or not(_assign.engage or _assign.move or _assign.request or _assign.cancel or _assign.set)then -self:T(self.lid..string.format("WARNING: %s, no keyword ENGAGE, MOVE, REQUEST, CANCEL or SET in mark text! Command will not be executed. Text:\n%s",self.groupname,Event.text)) -return -end -local _assigned=false -if _assign.everyone then -_assigned=true -else -for _,bat in pairs(_assign.battery)do -if self.groupname==bat then -_assigned=true -end -end -for _,alias in pairs(_assign.aliases)do -if self.alias==alias then -_assigned=true -end -end -for _,bat in pairs(_assign.cluster)do -for _,cluster in pairs(self.clusters)do -if cluster==bat then -_assigned=true -end -end -end -end -if not _assigned then -self:T3(self.lid..string.format("INFO: ARTY group %s was not addressed! Mark text:\n%s",self.groupname,Event.text)) -return -else -if self.Controllable and self.Controllable:IsAlive()then -else -self:T3(self.lid..string.format("INFO: ARTY group %s was addressed but is NOT alive! Mark text:\n%s",self.groupname,Event.text)) -return -end -end -if _assign.coord then -_coord=_assign.coord -end -local _validkey=self:_MarkerKeyAuthentification(Event.text) -if _assign.request and _validkey then -if _assign.requestammo then -self:_MarkRequestAmmo() -end -if _assign.requestmoves then -self:_MarkRequestMoves() -end -if _assign.requesttargets then -self:_MarkRequestTargets() -end -if _assign.requeststatus then -self:_MarkRequestStatus() -end -if _assign.requestrearming then -self:Rearm() -end -return -end -if _assign.cancel and _validkey then -if _assign.cancelmove and self.currentMove then -self.Controllable:ClearTasks() -self:Arrived() -elseif _assign.canceltarget and self.currentTarget then -self.currentTarget.engaged=self.currentTarget.engaged+1 -self:CeaseFire(self.currentTarget) -elseif _assign.cancelrearm and self:is("Rearming")then -local nammo=self:GetAmmo() -if nammo>0 then -self:Rearmed() -else -self:Winchester() -end -end -return -end -if _assign.set and _validkey then -if _assign.setrearmingplace and self.ismobile then -self:SetRearmingPlace(_coord) -_coord:RemoveMark(Event.idx) -_coord:MarkToCoalition(string.format("Rearming place for battery %s",self.groupname),self.coalition,false,string.format("New rearming place for battery %s defined.",self.groupname)) -if self.Debug then -_coord:SmokeOrange() -end -end -if _assign.setrearminggroup then -_coord:RemoveMark(Event.idx) -local rearminggroupcoord=_assign.setrearminggroup:GetCoordinate() -rearminggroupcoord:MarkToCoalition(string.format("Rearming group for battery %s",self.groupname),self.coalition,false,string.format("New rearming group for battery %s defined.",self.groupname)) -self:SetRearmingGroup(_assign.setrearminggroup) -if self.Debug then -rearminggroupcoord:SmokeOrange() -end -end -return -end -if _validkey then -_coord:RemoveMark(Event.idx) -local _id=UTILS._MarkID+1 -if _assign.move then -local _name=self:_MarkMoveName(_id) -local text=string.format("%s, received new relocation assignment.",self.alias) -text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) -MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug) -local _movename=self:AssignMoveCoord(_coord,_assign.time,_assign.speed,_assign.onroad,_assign.movecanceltarget,_name,true) -if _movename~=nil then -local _mid=self:_GetMoveIndexByName(_movename) -local _move=self.moves[_mid] -local clock=tostring(self:_SecondsToClock(_move.time)) -local _markertext=_movename..string.format(", Time=%s, Speed=%d km/h, Use Roads=%s.",clock,_move.speed,tostring(_move.onroad)) -local _randomcoord=_coord:GetRandomCoordinateInRadius(100) -_randomcoord:MarkToCoalition(_markertext,batterycoalition,self.markreadonly or _assign.readonly) -else -local text=string.format("%s, relocation not possible.",self.alias) -MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug) -end -else -local _name=self:_MarkTargetName(_id) -local text=string.format("%s, received new target assignment.",self.alias) -text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) -if _assign.time then -text=text..string.format("\nTime %s",_assign.time) -end -if _assign.prio then -text=text..string.format("\nPrio %d",_assign.prio) -end -if _assign.radius then -text=text..string.format("\nRadius %d m",_assign.radius) -end -if _assign.nshells then -text=text..string.format("\nShots %d",_assign.nshells) -end -if _assign.maxengage then -text=text..string.format("\nEngagements %d",_assign.maxengage) -end -if _assign.weapontype then -text=text..string.format("\nWeapon %s",self:_WeaponTypeName(_assign.weapontype)) -end -MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug) -local _targetname=self:AssignTargetCoord(_coord,_assign.prio,_assign.radius,_assign.nshells,_assign.maxengage,_assign.time,_assign.weapontype,_name,true) -if _targetname~=nil then -local _tid=self:_GetTargetIndexByName(_targetname) -local _target=self.targets[_tid] -local clock=tostring(self:_SecondsToClock(_target.time)) -local weapon=self:_WeaponTypeName(_target.weapontype) -local _markertext=_targetname..string.format(", Priority=%d, Radius=%d m, Shots=%d, Engagements=%d, Weapon=%s, Time=%s",_target.prio,_target.radius,_target.nshells,_target.maxengage,weapon,clock) -local _randomcoord=_coord:GetRandomCoordinateInRadius(250) -_randomcoord:MarkToCoalition(_markertext,batterycoalition,self.markreadonly or _assign.readonly) -end -end -end -end -end -end -function ARTY:OnEventDead(EventData) -self:F(EventData) -local _name=self.groupname -if EventData and EventData.IniGroupName and EventData.IniGroupName==_name then -local unitname=tostring(EventData.IniUnitName) -self:T(self.lid..string.format("%s: Captured dead event for unit %s.",_name,unitname)) -self:Dead(unitname) -end -end -function ARTY:onafterStatus(Controllable,From,Event,To) -self:_EventFromTo("onafterStatus",Event,From,To) -local nammo,nshells,nrockets,nmissiles=self:GetAmmo() -if self.iscargo and self.cargogroup then -if self.cargogroup:IsLoaded()and not self:is("InTransit")then -self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.",self.alias)) -self:Loaded() -elseif self.cargogroup:IsUnLoaded()then -self:T(self.lid..string.format("Group %s has been unloaded from the carrier.",self.alias)) -self:UnLoaded() -end -end -local fsmstate=self:GetState() -self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d",fsmstate,nammo,nshells,self.Nsmoke,self.Nillu,self.Nukes,self.nukewarhead/1000000,nrockets,nmissiles)) -if self.Controllable and self.Controllable:IsAlive()then -if self.Debug then -self:_StatusReport() -end -if self:is("Moving")then -self:T2(self.lid..string.format("%s: Moving",Controllable:GetName())) -end -if self:is("Rearming")then -local _rearmed=self:_CheckRearmed() -if _rearmed then -self:T2(self.lid..string.format("%s: Rearming ==> Rearmed",Controllable:GetName())) -self:Rearmed() -end -end -if self:is("Rearmed")then -local distance=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) -self:T2(self.lid..string.format("%s: Rearmed. Distance ARTY to InitalCoord = %d m",Controllable:GetName(),distance)) -if distance<=self.RearmingDistance then -self:T2(self.lid..string.format("%s: Rearmed ==> CombatReady",Controllable:GetName())) -self:CombatReady() -end -end -if self:is("Arrived")then -self:T2(self.lid..string.format("%s: Arrived ==> CombatReady",Controllable:GetName())) -self:CombatReady() -end -if self:is("Firing")then -self:_CheckShootingStarted() -end -self:_CheckTargetsInRange() -local notpossible={} -for i=1,#self.targets do -local _target=self.targets[i] -local possible=self:_CheckWeaponTypePossible(_target) -if not possible then -table.insert(notpossible,_target.name) -end -end -for _,targetname in pairs(notpossible)do -self:E(self.lid..string.format("%s: Removing target %s because requested weapon is not possible with this type of unit.",self.groupname,targetname)) -self:RemoveTarget(targetname) -end -local _timedTarget=self:_CheckTimedTargets() -local _normalTarget=self:_CheckNormalTargets() -local _move=self:_CheckMoves() -if _move then -self:Move(_move) -elseif _timedTarget then -if self.currentTarget then -self:CeaseFire(self.currentTarget) -end -self:OpenFire(_timedTarget) -elseif _normalTarget then -self:OpenFire(_normalTarget) -end -local gotsome=false -if#self.targets>0 then -for i=1,#self.targets do -local _target=self.targets[i] -if self:_CheckWeaponTypeAvailable(_target)>0 then -gotsome=true -end -end -else -gotsome=true -end -if(nammo==0 or not gotsome)and not(self:is("Moving")or self:is("Rearming")or self:is("OutOfAmmo"))then -self:Winchester() -end -if self:is("OutOfAmmo")then -self:T2(self.lid..string.format("%s: OutOfAmmo ==> Rearm ==> Rearming",Controllable:GetName())) -self:Rearm() -end -self:__Status(self.StatusInterval) -elseif self.iscargo then -if self.cargogroup and self.cargogroup:IsAlive()then -if self:is("InTransit")then -self:__Status(-5) -end -end -else -self:E(self.lid..string.format("Arty group %s is not alive!",self.groupname)) -end -end -function ARTY:onbeforeLoaded(Controllable,From,Event,To) -if self.currentTarget then -self:CeaseFire(self.currentTarget) -end -return true -end -function ARTY:onafterUnLoaded(Controllable,From,Event,To) -self:CombatReady() -end -function ARTY:onenterCombatReady(Controllable,From,Event,To) -self:_EventFromTo("onenterCombatReady",Event,From,To) -self:T3(self.lid..string.format("onenterComabReady, from=%s, event=%s, to=%s",From,Event,To)) -end -function ARTY:onbeforeOpenFire(Controllable,From,Event,To,target) -self:_EventFromTo("onbeforeOpenFire",Event,From,To) -if self.currentTarget then -self:E(self.lid..string.format("ERROR: Group %s already has a target %s!",self.groupname,self.currentTarget.name)) -return false -end -if not self:_TargetInRange(target)then -self:E(self.lid..string.format("ERROR: Group %s, target %s is out of range!",self.groupname,self.currentTarget.name)) -return false -end -local nfire=self:_CheckWeaponTypeAvailable(target) -target.nshells=math.min(target.nshells,nfire) -if target.nshells<1 then -local text=string.format("%s, no ammo left to engage target %s with selected weapon type %s.") -return false -end -return true -end -function ARTY:onafterOpenFire(Controllable,From,Event,To,target) -self:_EventFromTo("onafterOpenFire",Event,From,To) -local id=self:_GetTargetIndexByName(target.name) -if id then -self.targets[id].underfire=true -self.currentTarget=target -self.currentTarget.Tassigned=timer.getTime() -end -local range=Controllable:GetCoordinate():Get2DDistance(target.coord) -local Nammo,Nshells,Nrockets,Nmissiles=self:GetAmmo() -local nfire=Nammo -local _type="shots" -if target.weapontype==ARTY.WeaponType.Auto then -nfire=Nammo -_type="shots" -elseif target.weapontype==ARTY.WeaponType.Cannon then -nfire=Nshells -_type="shells" -elseif target.weapontype==ARTY.WeaponType.TacticalNukes then -nfire=self.Nukes -_type="nuclear shells" -elseif target.weapontype==ARTY.WeaponType.IlluminationShells then -nfire=self.Nillu -_type="illumination shells" -elseif target.weapontype==ARTY.WeaponType.SmokeShells then -nfire=self.Nsmoke -_type="smoke shells" -elseif target.weapontype==ARTY.WeaponType.Rockets then -nfire=Nrockets -_type="rockets" -elseif target.weapontype==ARTY.WeaponType.CruiseMissile then -nfire=Nmissiles -_type="cruise missiles" -end -target.nshells=math.min(target.nshells,nfire) -local text=string.format("%s, opening fire on target %s with %d %s. Distance %.1f km.",Controllable:GetName(),target.name,target.nshells,_type,range/1000) -self:T(self.lid..text) -MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report) -if target.attackgroup then -self:_AttackGroup(target) -else -self:_FireAtCoord(target.coord,target.radius,target.nshells,target.weapontype) -end -end -function ARTY:onafterCeaseFire(Controllable,From,Event,To,target) -self:_EventFromTo("onafterCeaseFire",Event,From,To) -if target then -local text=string.format("%s, ceasing fire on target %s.",Controllable:GetName(),target.name) -self:T(self.lid..text) -MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report) -local id=self:_GetTargetIndexByName(target.name) -if id then -if self.Nshots>0 then -self.targets[id].engaged=self.targets[id].engaged+1 -self.targets[id].time=nil -end -self.targets[id].underfire=false -end -if target.engaged>=target.maxengage then -self:RemoveTarget(target.name) -end -self.Controllable:OptionROEHoldFire() -self.Controllable:ClearTasks() -else -self:E(self.lid..string.format("ERROR: No target in cease fire for group %s.",self.groupname)) -end -self.Nshots=0 -self.currentTarget=nil -end -function ARTY:onafterWinchester(Controllable,From,Event,To) -self:_EventFromTo("onafterWinchester",Event,From,To) -local text=string.format("%s, winchester!",Controllable:GetName()) -self:T(self.lid..text) -MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) -end -function ARTY:onbeforeRearm(Controllable,From,Event,To) -self:_EventFromTo("onbeforeRearm",Event,From,To) -local _rearmed=self:_CheckRearmed() -if _rearmed then -self:T(self.lid..string.format("%s, group is already armed to the teeth. Rearming request denied!",self.groupname)) -return false -else -self:T(self.lid..string.format("%s, group might be rearmed.",self.groupname)) -end -if self.RearmingGroup and self.RearmingGroup:IsAlive()then -return true -elseif self.RearmingPlaceCoord then -return true -else -return false -end -end -function ARTY:onafterRearm(Controllable,From,Event,To) -self:_EventFromTo("onafterRearm",Event,From,To) -local coordARTY=self.Controllable:GetCoordinate() -self.InitialCoord=coordARTY -local coordRARM=nil -if self.RearmingGroup then -coordRARM=self.RearmingGroup:GetCoordinate() -self.RearmingGroupCoord=coordRARM -end -if self.RearmingGroup and self.RearmingPlaceCoord and self.ismobile then -local text=string.format("%s, %s, request rearming at rearming place.",Controllable:GetName(),self.RearmingGroup:GetName()) -self:T(self.lid..text) -MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) -local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord) -local dR=coordRARM:Get2DDistance(self.RearmingPlaceCoord) -if dA>self.RearmingDistance then -local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord,self.RearmingDistance/4,self.RearmingDistance/2) -self:AssignMoveCoord(_tocoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE TO REARMING PLACE",true) -end -if dR>self.RearmingDistance then -local ToCoord=self:_VicinityCoord(self.RearmingPlaceCoord,self.RearmingDistance/4,self.RearmingDistance/2) -self:_Move(self.RearmingGroup,ToCoord,self.RearmingGroupSpeed,self.RearmingGroupOnRoad) -end -elseif self.RearmingGroup then -local text=string.format("%s, %s, request rearming.",Controllable:GetName(),self.RearmingGroup:GetName()) -self:T(self.lid..text) -MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) -local distance=coordARTY:Get2DDistance(coordRARM) -if distance>self.RearmingDistance then -self:_Move(self.RearmingGroup,self:_VicinityCoord(coordARTY),self.RearmingGroupSpeed,self.RearmingGroupOnRoad) -end -elseif self.RearmingPlaceCoord then -local text=string.format("%s, moving to rearming place.",Controllable:GetName()) -self:T(self.lid..text) -MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) -local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord) -if dA>self.RearmingDistance then -local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord) -self:AssignMoveCoord(_tocoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE TO REARMING PLACE",true) -end -end -end -function ARTY:onafterRearmed(Controllable,From,Event,To) -self:_EventFromTo("onafterRearmed",Event,From,To) -local text=string.format("%s, rearming complete.",Controllable:GetName()) -self:T(self.lid..text) -MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) -self.Nukes=self.Nukes0 -self.Nillu=self.Nillu0 -self.Nsmoke=self.Nsmoke0 -local dist=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) -if dist>self.RearmingDistance then -self:AssignMoveCoord(self.InitialCoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE REARMING COMPLETE",true) -end -if self.RearmingGroup and self.RearmingGroup:IsAlive()then -local d=self.RearmingGroup:GetCoordinate():Get2DDistance(self.RearmingGroupCoord) -if d>self.RearmingDistance then -self:_Move(self.RearmingGroup,self.RearmingGroupCoord,self.RearmingGroupSpeed,self.RearmingGroupOnRoad) -else -self.RearmingGroup:ClearTasks() -end -end -end -function ARTY:_CheckRearmed() -self:F2() -local nammo,nshells,nrockets,nmissiles=self:GetAmmo() -local units=self.Controllable:GetUnits() -local nunits=0 -if units then -nunits=#units -end -local FullAmmo=self.Nammo0*nunits/self.IniGroupStrength -local _rearmpc=nammo/FullAmmo*100 -if _rearmpc>1 then -local text=string.format("%s, rearming %d %% complete.",self.alias,_rearmpc) -self:T(self.lid..text) -MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) -end -if nammo>=FullAmmo then -return true -else -return false -end -end -function ARTY:onbeforeMove(Controllable,From,Event,To,move) -self:_EventFromTo("onbeforeMove",Event,From,To) -if not self.ismobile then -return false -end -if self.currentTarget then -if move.cancel then -self:CeaseFire(self.currentTarget) -else -return false -end -end -return true -end -function ARTY:onafterMove(Controllable,From,Event,To,move) -self:_EventFromTo("onafterMove",Event,From,To) -self.Controllable:OptionAlarmStateGreen() -self.Controllable:OptionROEHoldFire() -local _Speed=math.min(move.speed,self.SpeedMax) -if self.Debug then -move.coord:SmokeRed() -end -self.currentMove=move -self:_Move(self.Controllable,move.coord,move.speed,move.onroad) -end -function ARTY:onafterArrived(Controllable,From,Event,To) -self:_EventFromTo("onafterArrived",Event,From,To) -self.Controllable:OptionAlarmStateAuto() -local text=string.format("%s, arrived at destination.",Controllable:GetName()) -self:T(self.lid..text) -MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) -if self.currentMove then -self:RemoveMove(self.currentMove.name) -self.currentMove=nil -end -end -function ARTY:onafterNewTarget(Controllable,From,Event,To,target) -self:_EventFromTo("onafterNewTarget",Event,From,To) -local text=string.format("Adding new target %s.",target.name) -MESSAGE:New(text,5):ToAllIf(self.Debug) -self:T(self.lid..text) -end -function ARTY:onafterNewMove(Controllable,From,Event,To,move) -self:_EventFromTo("onafterNewTarget",Event,From,To) -local text=string.format("Adding new move %s.",move.name) -MESSAGE:New(text,5):ToAllIf(self.Debug) -self:T(self.lid..text) -end -function ARTY:onafterDead(Controllable,From,Event,To,Unitname) -self:_EventFromTo("onafterDead",Event,From,To) -local nunits=self.Controllable:CountAliveUnits() -local text=string.format("%s, our unit %s just died! %d units left.",self.groupname,Unitname,nunits) -MESSAGE:New(text,5):ToAllIf(self.Debug) -self:I(self.lid..text) -if nunits==0 then -if self.currentTarget then -self:CeaseFire(self.currentTarget) -end -if self.respawnafterdeath then -if not self.respawning then -self.respawning=true -self:__Respawn(self.respawndelay or 1) -end -else -self:Stop() -end -end -end -function ARTY:onafterRespawn(Controllable,From,Event,To) -self:_EventFromTo("onafterRespawn",Event,From,To) -self:I("Respawning arty group") -local group=self.Controllable -self.Controllable=group:Respawn() -self.respawning=false -self:__Status(-1) -end -function ARTY:onafterStop(Controllable,From,Event,To) -self:_EventFromTo("onafterStop",Event,From,To) -self:I(self.lid..string.format("Stopping ARTY FSM for group %s.",tostring(Controllable:GetName()))) -if self.currentTarget then -self:CeaseFire(self.currentTarget) -end -self:UnHandleEvent(EVENTS.Shot) -self:UnHandleEvent(EVENTS.Dead) -end -function ARTY:_FireAtCoord(coord,radius,nshells,weapontype) -self:F({coord=coord,radius=radius,nshells=nshells}) -local group=self.Controllable -if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then -weapontype=ARTY.WeaponType.Cannon -end -group:OptionROEOpenFire() -local vec2=coord:GetVec2() -local fire=group:TaskFireAtPoint(vec2,radius,nshells,weapontype) -group:SetTask(fire) -end -function ARTY:_AttackGroup(target) -local group=self.Controllable -local weapontype=target.weapontype -if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then -weapontype=ARTY.WeaponType.Cannon -end -group:OptionROEOpenFire() -local targetgroup=GROUP:FindByName(target.name) -local fire=group:TaskAttackGroup(targetgroup,weapontype,AI.Task.WeaponExpend.ONE,1) -group:SetTask(fire) -end -function ARTY:_NuclearBlast(_coord) -local S0=self.nukewarhead -local R0=self.nukerange -local N0=self.nukefires -_coord:Explosion(S0) -_coord:BigSmokeAndFireHuge() -local _fires={} -for i=1,N0 do -local _fire=_coord:GetRandomCoordinateInRadius(R0) -local _dist=_fire:Get2DDistance(_coord) -table.insert(_fires,{distance=_dist,coord=_fire}) -end -local _sort=function(a,b)return a.distance_nmax -if _gotit then -self:AssignMoveCoord(_new,nil,nil,false,false,"RELOCATION MOVE AFTER FIRING") -end -end -function ARTY:GetAmmo(display) -self:F3({display=display}) -if display==nil then -display=false -end -local nammo=0 -local nshells=0 -local nrockets=0 -local nmissiles=0 -local units=self.Controllable:GetUnits() -if units==nil then -return nammo,nshells,nrockets,nmissiles -end -for _,_unit in pairs(units)do -local unit=_unit -if unit then -local text=string.format("ARTY group %s - unit %s:\n",self.groupname,unit:GetName()) -local ammotable=unit:GetAmmo() -if ammotable~=nil then -local weapons=#ammotable -if display then -self:I(self.lid..string.format("Number of weapons %d.",weapons)) -self:I({ammotable=ammotable}) -self:I(self.lid.."Ammotable:") -for id,bla in pairs(ammotable)do -self:I({id=id,ammo=bla}) -end -end -for w=1,weapons do -local Nammo=ammotable[w]["count"] -local Tammo=ammotable[w]["desc"]["typeName"] -local _weaponString=self:_split(Tammo,"%.") -local _weaponName=_weaponString[#_weaponString] -local Category=ammotable[w].desc.category -local MissileCategory=nil -if Category==Weapon.Category.MISSILE then -MissileCategory=ammotable[w].desc.missileCategory -end -local _gotshell=false -if#self.ammoshells>0 then -for _,_type in pairs(self.ammoshells)do -if string.match(Tammo,_type)and Category==Weapon.Category.SHELL then -_gotshell=true -end -end -else -if Category==Weapon.Category.SHELL then -_gotshell=true -end -end -local _gotrocket=false -if#self.ammorockets>0 then -for _,_type in pairs(self.ammorockets)do -if string.match(Tammo,_type)and Category==Weapon.Category.ROCKET then -_gotrocket=true -end -end -else -if Category==Weapon.Category.ROCKET then -_gotrocket=true -end -end -local _gotmissile=false -if#self.ammomissiles>0 then -for _,_type in pairs(self.ammomissiles)do -if string.match(Tammo,_type)and Category==Weapon.Category.MISSILE then -_gotmissile=true -end -end -else -if Category==Weapon.Category.MISSILE then -_gotmissile=true -end -end -if _gotshell then -nshells=nshells+Nammo -text=text..string.format("- %d shells of type %s\n",Nammo,_weaponName) -elseif _gotrocket then -nrockets=nrockets+Nammo -text=text..string.format("- %d rockets of type %s\n",Nammo,_weaponName) -elseif _gotmissile then -if MissileCategory==Weapon.MissileCategory.CRUISE then -nmissiles=nmissiles+Nammo -end -text=text..string.format("- %d %s missiles of type %s\n",Nammo,self:_MissileCategoryName(MissileCategory),_weaponName) -else -text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n",Nammo,Tammo,Category,tostring(MissileCategory)) -end -end -end -if display then -self:I(self.lid..text) -else -self:T3(self.lid..text) -end -MESSAGE:New(text,10):ToAllIf(display) -end -end -nammo=nshells+nrockets+nmissiles -return nammo,nshells,nrockets,nmissiles -end -function ARTY:_MissileCategoryName(categorynumber) -local cat="unknown" -if categorynumber==Weapon.MissileCategory.AAM then -cat="air-to-air" -elseif categorynumber==Weapon.MissileCategory.SAM then -cat="surface-to-air" -elseif categorynumber==Weapon.MissileCategory.BM then -cat="ballistic" -elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then -cat="anti-ship" -elseif categorynumber==Weapon.MissileCategory.CRUISE then -cat="cruise" -elseif categorynumber==Weapon.MissileCategory.OTHER then -cat="other" -end -return cat -end -function ARTY:_MarkerKeyAuthentification(text) -local batterycoalition=self.coalition -local mykey=nil -if self.markkey~=nil then -local keywords=self:_split(text,",") -for _,key in pairs(keywords)do -local s=self:_split(key," ") -local val=s[2] -if key:lower():find("key")then -mykey=tonumber(val) -self:T(self.lid..string.format("Authorisation Key=%s.",val)) -end -end -end -local _validkey=true -if self.markkey~=nil then -_validkey=false -if mykey~=nil then -_validkey=self.markkey==mykey -end -self:T2(self.lid..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s",self.groupname,tostring(self.markkey),tostring(mykey),tostring(_validkey))) -local text="" -if mykey==nil then -text=string.format("%s, authorization required but did not receive a key!",self.alias) -elseif _validkey==false then -text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!",self.alias,tostring(mykey)) -elseif _validkey==true then -text=string.format("%s, authentification successful!",self.alias) -end -MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug) -end -return _validkey -end -function ARTY:_Markertext(text) -self:F(text) -local assignment={} -assignment.battery={} -assignment.aliases={} -assignment.cluster={} -assignment.everyone=false -assignment.move=false -assignment.engage=false -assignment.request=false -assignment.cancel=false -assignment.set=false -assignment.readonly=false -assignment.movecanceltarget=false -assignment.cancelmove=false -assignment.canceltarget=false -assignment.cancelrearm=false -assignment.setrearmingplace=false -assignment.setrearminggroup=false -if text:lower():find("arty engage")or text:lower():find("arty attack")then -assignment.engage=true -elseif text:lower():find("arty move")or text:lower():find("arty relocate")then -assignment.move=true -elseif text:lower():find("arty request")then -assignment.request=true -elseif text:lower():find("arty cancel")then -assignment.cancel=true -elseif text:lower():find("arty set")then -assignment.set=true -else -self:E(self.lid..'ERROR: Neither "ARTY ENGAGE" nor "ARTY MOVE" nor "ARTY RELOCATE" nor "ARTY REQUEST" nor "ARTY CANCEL" nor "ARTY SET" keyword specified!') -return nil -end -local keywords=self:_split(text,",") -self:T({keywords=keywords}) -for _,keyphrase in pairs(keywords)do -local str=self:_split(keyphrase," ") -local key=str[1] -local val=str[2] -self:T3(self.lid..string.format("%s, keyphrase = %s, key = %s, val = %s",self.groupname,tostring(keyphrase),tostring(key),tostring(val))) -if key:lower():find("battery")then -local v=self:_split(keyphrase,'"') -for i=2,#v,2 do -table.insert(assignment.battery,v[i]) -self:T2(self.lid..string.format("Key Battery=%s.",v[i])) -end -elseif key:lower():find("alias")then -local v=self:_split(keyphrase,'"') -for i=2,#v,2 do -table.insert(assignment.aliases,v[i]) -self:T2(self.lid..string.format("Key Aliases=%s.",v[i])) -end -elseif key:lower():find("cluster")then -local v=self:_split(keyphrase,'"') -for i=2,#v,2 do -table.insert(assignment.cluster,v[i]) -self:T2(self.lid..string.format("Key Cluster=%s.",v[i])) -end -elseif keyphrase:lower():find("everyone")or keyphrase:lower():find("all batteries")or keyphrase:lower():find("allbatteries")then -assignment.everyone=true -self:T(self.lid..string.format("Key Everyone=true.")) -elseif keyphrase:lower():find("irrevocable")or keyphrase:lower():find("readonly")then -assignment.readonly=true -self:T2(self.lid..string.format("Key Readonly=true.")) -elseif(assignment.engage or assignment.move)and key:lower():find("time")then -if val:lower():find("now")then -assignment.time=self:_SecondsToClock(timer.getTime0()+2) -else -assignment.time=val -end -self:T2(self.lid..string.format("Key Time=%s.",val)) -elseif assignment.engage and key:lower():find("shot")then -assignment.nshells=tonumber(val) -self:T(self.lid..string.format("Key Shot=%s.",val)) -elseif assignment.engage and key:lower():find("prio")then -assignment.prio=tonumber(val) -self:T2(string.format("Key Prio=%s.",val)) -elseif assignment.engage and key:lower():find("maxengage")then -assignment.maxengage=tonumber(val) -self:T2(self.lid..string.format("Key Maxengage=%s.",val)) -elseif assignment.engage and key:lower():find("radius")then -assignment.radius=tonumber(val) -self:T2(self.lid..string.format("Key Radius=%s.",val)) -elseif assignment.engage and key:lower():find("weapon")then -if val:lower():find("cannon")then -assignment.weapontype=ARTY.WeaponType.Cannon -elseif val:lower():find("rocket")then -assignment.weapontype=ARTY.WeaponType.Rockets -elseif val:lower():find("missile")then -assignment.weapontype=ARTY.WeaponType.CruiseMissile -elseif val:lower():find("nuke")then -assignment.weapontype=ARTY.WeaponType.TacticalNukes -elseif val:lower():find("illu")then -assignment.weapontype=ARTY.WeaponType.IlluminationShells -elseif val:lower():find("smoke")then -assignment.weapontype=ARTY.WeaponType.SmokeShells -else -assignment.weapontype=ARTY.WeaponType.Auto -end -self:T2(self.lid..string.format("Key Weapon=%s.",val)) -elseif(assignment.move or assignment.set)and key:lower():find("speed")then -assignment.speed=tonumber(val) -self:T2(self.lid..string.format("Key Speed=%s.",val)) -elseif(assignment.move or assignment.set)and(keyphrase:lower():find("on road")or keyphrase:lower():find("onroad")or keyphrase:lower():find("use road"))then -assignment.onroad=true -self:T2(self.lid..string.format("Key Onroad=true.")) -elseif assignment.move and(keyphrase:lower():find("cancel target")or keyphrase:lower():find("canceltarget"))then -assignment.movecanceltarget=true -self:T2(self.lid..string.format("Key Cancel Target (before move)=true.")) -elseif assignment.request and keyphrase:lower():find("rearm")then -assignment.requestrearming=true -self:T2(self.lid..string.format("Key Request Rearming=true.")) -elseif assignment.request and keyphrase:lower():find("ammo")then -assignment.requestammo=true -self:T2(self.lid..string.format("Key Request Ammo=true.")) -elseif assignment.request and keyphrase:lower():find("target")then -assignment.requesttargets=true -self:T2(self.lid..string.format("Key Request Targets=true.")) -elseif assignment.request and keyphrase:lower():find("status")then -assignment.requeststatus=true -self:T2(self.lid..string.format("Key Request Status=true.")) -elseif assignment.request and(keyphrase:lower():find("move")or keyphrase:lower():find("relocation"))then -assignment.requestmoves=true -self:T2(self.lid..string.format("Key Request Moves=true.")) -elseif assignment.cancel and(keyphrase:lower():find("engagement")or keyphrase:lower():find("attack")or keyphrase:lower():find("target"))then -assignment.canceltarget=true -self:T2(self.lid..string.format("Key Cancel Target=true.")) -elseif assignment.cancel and(keyphrase:lower():find("move")or keyphrase:lower():find("relocation"))then -assignment.cancelmove=true -self:T2(self.lid..string.format("Key Cancel Move=true.")) -elseif assignment.cancel and keyphrase:lower():find("rearm")then -assignment.cancelrearm=true -self:T2(self.lid..string.format("Key Cancel Rearm=true.")) -elseif assignment.set and keyphrase:lower():find("rearming place")then -assignment.setrearmingplace=true -self:T(self.lid..string.format("Key Set Rearming Place=true.")) -elseif assignment.set and keyphrase:lower():find("rearming group")then -local v=self:_split(keyphrase,'"') -local groupname=v[2] -local group=GROUP:FindByName(groupname) -if group and group:IsAlive()then -assignment.setrearminggroup=group -end -self:T2(self.lid..string.format("Key Set Rearming Group = %s.",tostring(groupname))) -elseif key:lower():find("lldms")then -local _flat="%d+:%d+:%d+%s*[N,S]" -local _flon="%d+:%d+:%d+%s*[W,E]" -local _lat=keyphrase:match(_flat) -local _lon=keyphrase:match(_flon) -self:T2(self.lid..string.format("Key LLDMS: lat=%s, long=%s format=DMS",_lat,_lon)) -if _lat and _lon then -local _latitude,_longitude=self:_LLDMS2DD(_lat,_lon) -self:T2(self.lid..string.format("Key LLDMS: lat=%.3f, long=%.3f format=DD",_latitude,_longitude)) -if _latitude and _longitude then -assignment.coord=COORDINATE:NewFromLLDD(_latitude,_longitude) -end -end -end -end -return assignment -end -function ARTY:_MarkRequestAmmo() -self:GetAmmo(true) -end -function ARTY:_MarkRequestStatus() -self:_StatusReport(true) -end -function ARTY:_MarkRequestMoves() -local text=string.format("%s, relocations:",self.groupname) -if#self.moves>0 then -for _,move in pairs(self.moves)do -if self.currentMove and move.name==self.currentMove.name then -text=text..string.format("\n- %s (current)",self:_MoveInfo(move)) -else -text=text..string.format("\n- %s",self:_MoveInfo(move)) -end -end -else -text=text..string.format("\n- no queued relocations") -end -MESSAGE:New(text,20):Clear():ToCoalition(self.coalition) -end -function ARTY:_MarkRequestTargets() -local text=string.format("%s, targets:",self.groupname) -if#self.targets>0 then -for _,target in pairs(self.targets)do -if self.currentTarget and target.name==self.currentTarget.name then -text=text..string.format("\n- %s (current)",self:_TargetInfo(target)) -else -text=text..string.format("\n- %s",self:_TargetInfo(target)) -end -end -else -text=text..string.format("\n- no queued targets") -end -MESSAGE:New(text,20):Clear():ToCoalition(self.coalition) -end -function ARTY:_MarkTargetName(markerid) -return string.format("BATTERY=%s, Marked Target ID=%d",self.groupname,markerid) -end -function ARTY:_MarkMoveName(markerid) -return string.format("BATTERY=%s, Marked Relocation ID=%d",self.groupname,markerid) -end -function ARTY:_GetMarkIDfromName(name) -local keywords=self:_split(name,",") -local battery=nil -local markTID=nil -local markMID=nil -for _,key in pairs(keywords)do -local str=self:_split(key,"=") -local par=str[1] -local val=str[2] -if par:find("BATTERY")then -battery=val -end -if par:find("Marked Target ID")then -markTID=tonumber(val) -end -if par:find("Marked Relocation ID")then -markMID=tonumber(val) -end -end -return battery,markTID,markMID -end -function ARTY:_SortTargetQueuePrio() -self:F2() -local function _sort(a,b) -return(a.engaged_target.engaged and self:_TargetInRange(_target)and self:_CheckWeaponTypeAvailable(_target)>0 then -self:T2(self.lid..string.format("Found NORMAL target %s",self:_TargetInfo(_target))) -return _target -end -end -return nil -end -function ARTY:_CheckTimedTargets() -self:F3() -local Tnow=timer.getAbsTime() -self:_SortQueueTime(self.targets) -if self:is("Rearming")then -return nil -end -for i=1,#self.targets do -local _target=self.targets[i] -self:T3(self.lid..string.format("Check TIMED target %d: %s",i,self:_TargetInfo(_target))) -if _target.time and Tnow>=_target.time and _target.underfire==false and self:_TargetInRange(_target)and self:_CheckWeaponTypeAvailable(_target)>0 then -if self.currentTarget then -if self.currentTarget.prio>_target.prio then -self:T2(self.lid..string.format("Found TIMED HIGH PRIO target %s.",self:_TargetInfo(_target))) -return _target -end -else -self:T2(self.lid..string.format("Found TIMED target %s.",self:_TargetInfo(_target))) -return _target -end -end -end -return nil -end -function ARTY:_CheckMoves() -self:F3() -local Tnow=timer.getAbsTime() -self:_SortQueueTime(self.moves) -local firing=false -if self.currentTarget then -firing=true -end -for i=1,#self.moves do -local _move=self.moves[i] -if string.find(_move.name,"REARMING MOVE")and((self.currentMove and self.currentMove.name~=_move.name)or self.currentMove==nil)then -return _move -elseif(Tnow>=_move.time)and(firing==false or _move.cancel)and(not self.currentMove)and(not self:is("Rearming"))then -return _move -end -end -return nil -end -function ARTY:_CheckShootingStarted() -self:F2() -if self.currentTarget then -local Tnow=timer.getTime() -local name=self.currentTarget.name -local dt=Tnow-self.currentTarget.Tassigned -if self.Nshots==0 then -self:T(self.lid..string.format("%s, waiting for %d seconds for first shot on target %s.",self.groupname,dt,name)) -end -if dt>self.WaitForShotTime and(self.Nshots==0 or self.currentTarget.nshells>=self.Nshots)then -self:T(self.lid..string.format("%s, no shot event after %d seconds. Removing current target %s from list.",self.groupname,self.WaitForShotTime,name)) -self:CeaseFire(self.currentTarget) -self:RemoveTarget(name) -end -end -end -function ARTY:_GetTargetIndexByName(name) -self:F2(name) -for i=1,#self.targets do -local targetname=self.targets[i].name -self:T3(self.lid..string.format("Have target with name %s. Index = %d",targetname,i)) -if targetname==name then -self:T2(self.lid..string.format("Found target with name %s. Index = %d",name,i)) -return i -end -end -self:T2(self.lid..string.format("WARNING: Target with name %s could not be found. (This can happen.)",name)) -return nil -end -function ARTY:_GetMoveIndexByName(name) -self:F2(name) -for i=1,#self.moves do -local movename=self.moves[i].name -self:T3(self.lid..string.format("Have move with name %s. Index = %d",movename,i)) -if movename==name then -self:T2(self.lid..string.format("Found move with name %s. Index = %d",name,i)) -return i -end -end -self:T2(self.lid..string.format("WARNING: Move with name %s could not be found. (This can happen.)",name)) -return nil -end -function ARTY:_CheckOutOfAmmo(targets) -local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo() -local _partlyoutofammo=false -for _,Target in pairs(targets)do -if Target.weapontype==ARTY.WeaponType.Auto and _nammo==0 then -self:T(self.lid..string.format("Group %s, auto weapon requested for target %s but all ammo is empty.",self.groupname,Target.name)) -_partlyoutofammo=true -elseif Target.weapontype==ARTY.WeaponType.Cannon and _nshells==0 then -self:T(self.lid..string.format("Group %s, cannons requested for target %s but shells empty.",self.groupname,Target.name)) -_partlyoutofammo=true -elseif Target.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes<=0 then -self:T(self.lid..string.format("Group %s, tactical nukes requested for target %s but nukes empty.",self.groupname,Target.name)) -_partlyoutofammo=true -elseif Target.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu<=0 then -self:T(self.lid..string.format("Group %s, illumination shells requested for target %s but illumination shells empty.",self.groupname,Target.name)) -_partlyoutofammo=true -elseif Target.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke<=0 then -self:T(self.lid..string.format("Group %s, smoke shells requested for target %s but smoke shells empty.",self.groupname,Target.name)) -_partlyoutofammo=true -elseif Target.weapontype==ARTY.WeaponType.Rockets and _nrockets==0 then -self:T(self.lid..string.format("Group %s, rockets requested for target %s but rockets empty.",self.groupname,Target.name)) -_partlyoutofammo=true -elseif Target.weapontype==ARTY.WeaponType.CruiseMissile and _nmissiles==0 then -self:T(self.lid..string.format("Group %s, cruise missiles requested for target %s but all missiles empty.",self.groupname,Target.name)) -_partlyoutofammo=true -end -end -return _partlyoutofammo -end -function ARTY:_CheckWeaponTypeAvailable(target) -local Nammo,Nshells,Nrockets,Nmissiles=self:GetAmmo() -local nfire=Nammo -if target.weapontype==ARTY.WeaponType.Auto then -nfire=Nammo -elseif target.weapontype==ARTY.WeaponType.Cannon then -nfire=Nshells -elseif target.weapontype==ARTY.WeaponType.TacticalNukes then -nfire=self.Nukes -elseif target.weapontype==ARTY.WeaponType.IlluminationShells then -nfire=self.Nillu -elseif target.weapontype==ARTY.WeaponType.SmokeShells then -nfire=self.Nsmoke -elseif target.weapontype==ARTY.WeaponType.Rockets then -nfire=Nrockets -elseif target.weapontype==ARTY.WeaponType.CruiseMissile then -nfire=Nmissiles -end -return nfire -end -function ARTY:_CheckWeaponTypePossible(target) -local possible=false -if target.weapontype==ARTY.WeaponType.Auto then -possible=self.Nammo0>0 -elseif target.weapontype==ARTY.WeaponType.Cannon then -possible=self.Nshells0>0 -elseif target.weapontype==ARTY.WeaponType.TacticalNukes then -possible=self.Nukes0>0 -elseif target.weapontype==ARTY.WeaponType.IlluminationShells then -possible=self.Nillu0>0 -elseif target.weapontype==ARTY.WeaponType.SmokeShells then -possible=self.Nsmoke0>0 -elseif target.weapontype==ARTY.WeaponType.Rockets then -possible=self.Nrockets0>0 -elseif target.weapontype==ARTY.WeaponType.CruiseMissile then -possible=self.Nmissiles0>0 -end -return possible -end -function ARTY:_CheckName(givennames,name,makeunique) -self:F2({givennames=givennames,name=name}) -local newname=name -local counter=1 -local n=1 -local nmax=100 -if makeunique==nil then -makeunique=true -end -repeat -local _unique=true -for _,_target in pairs(givennames)do -local _givenname=_target.name -if _givenname==newname then -_unique=false -end -self:T3(self.lid..string.format("%d: givenname = %s, newname=%s, unique = %s, makeunique = %s",n,tostring(_givenname),newname,tostring(_unique),tostring(makeunique))) -end -if _unique==false and makeunique==true then -newname=string.format("%s #%02d",name,counter) -counter=counter+1 -end -if _unique==false and makeunique==false then -self:T3(self.lid..string.format("Name %s is not unique. Return false.",tostring(newname))) -return name,false -end -n=n+1 -until(_unique or n==nmax) -self:T3(self.lid..string.format("Original name %s, new name = %s",name,newname)) -return newname,true -end -function ARTY:_TargetInRange(target,message) -self:F3(target) -if message==nil then -message=false -end -self:T3({controllable=self.Controllable,targetcoord=target.coord}) -local _dist=self.Controllable:GetCoordinate():Get2DDistance(target.coord) -local _inrange=true -local _tooclose=false -local _toofar=false -local text="" -if _distself.maxrange then -_inrange=false -_toofar=true -text=string.format("%s, target is out of range. Distance of %.1f km is greater than max range of %.1f km.",self.alias,_dist/1000,self.maxrange/1000) -end -if not _inrange then -self:T(self.lid..text) -MESSAGE:New(text,5):ToCoalitionIf(self.coalition,(self.report and message)or(self.Debug and message)) -end -local _remove=false -if not(self.ismobile or self.iscargo)and _inrange==false then -_remove=true -end -return _inrange,_toofar,_tooclose,_remove -end -function ARTY:_WeaponTypeName(tnumber) -self:F2(tnumber) -local name="unknown" -if tnumber==ARTY.WeaponType.Auto then -name="Auto" -elseif tnumber==ARTY.WeaponType.Cannon then -name="Cannons" -elseif tnumber==ARTY.WeaponType.Rockets then -name="Rockets" -elseif tnumber==ARTY.WeaponType.CruiseMissile then -name="Cruise Missiles" -elseif tnumber==ARTY.WeaponType.TacticalNukes then -name="Tactical Nukes" -elseif tnumber==ARTY.WeaponType.IlluminationShells then -name="Illumination Shells" -elseif tnumber==ARTY.WeaponType.SmokeShells then -name="Smoke Shells" -end -return name -end -function ARTY:_VicinityCoord(coord,rmin,rmax) -self:F2({coord=coord,rmin=rmin,rmax=rmax}) -rmin=rmin or 20 -rmax=rmax or 80 -local vec2=coord:GetRandomVec2InRadius(rmax,rmin) -local pops=COORDINATE:NewFromVec2(vec2) -self:T3(self.lid..string.format("Vicinity distance = %d (rmin=%d, rmax=%d)",pops:Get2DDistance(coord),rmin,rmax)) -return pops -end -function ARTY:_EventFromTo(BA,Event,From,To) -local text=string.format("%s: %s EVENT %s: %s --> %s",BA,self.groupname,Event,From,To) -self:T3(self.lid..text) -end -function ARTY:_split(str,sep) -self:F3({str=str,sep=sep}) -local result={} -local regex=("([^%s]+)"):format(sep) -for each in str:gmatch(regex)do -table.insert(result,each) -end -return result -end -function ARTY:_TargetInfo(target) -local clock=tostring(self:_SecondsToClock(target.time)) -local weapon=self:_WeaponTypeName(target.weapontype) -local _underfire=tostring(target.underfire) -return string.format("%s: prio=%d, radius=%d, nshells=%d, engaged=%d/%d, weapontype=%s, time=%s, underfire=%s, attackgroup=%s", -target.name,target.prio,target.radius,target.nshells,target.engaged,target.maxengage,weapon,clock,_underfire,tostring(target.attackgroup)) -end -function ARTY:_MoveInfo(move) -self:F3(move) -local _clock=self:_SecondsToClock(move.time) -return string.format("%s: time=%s, speed=%d, onroad=%s, cancel=%s",move.name,_clock,move.speed,tostring(move.onroad),tostring(move.cancel)) -end -function ARTY:_LLDMS2DD(l1,l2) -self:F2(l1,l2) -local _latlong={l1,l2} -local _latitude=nil -local _longitude=nil -for _,ll in pairs(_latlong)do -local _format="%d+:%d+:%d+" -local _ldms=ll:match(_format) -if _ldms then -local _dms=self:_split(_ldms,":") -local _deg=tonumber(_dms[1]) -local _min=tonumber(_dms[2]) -local _sec=tonumber(_dms[3]) -local function DMS2DD(d,m,s) -return d+m/60+s/3600 -end -if ll:match("N")then -_latitude=DMS2DD(_deg,_min,_sec) -elseif ll:match("S")then -_latitude=-DMS2DD(_deg,_min,_sec) -elseif ll:match("W")then -_longitude=-DMS2DD(_deg,_min,_sec) -elseif ll:match("E")then -_longitude=DMS2DD(_deg,_min,_sec) -end -local text=string.format("DMS %02d Deg %02d min %02d sec",_deg,_min,_sec) -self:T2(self.lid..text) -end -end -local text=string.format("\nLatitude %s",tostring(_latitude)) -text=text..string.format("\nLongitude %s",tostring(_longitude)) -self:T2(self.lid..text) -return _latitude,_longitude -end -function ARTY:_SecondsToClock(seconds) -self:F3({seconds=seconds}) -if seconds==nil then -return nil -end -local seconds=tonumber(seconds) -local _seconds=seconds%(60*60*24) -if seconds<=0 then -return nil -else -local hours=string.format("%02.f",math.floor(_seconds/3600)) -local mins=string.format("%02.f",math.floor(_seconds/60-(hours*60))) -local secs=string.format("%02.f",math.floor(_seconds-hours*3600-mins*60)) -local days=string.format("%d",seconds/(60*60*24)) -return hours..":"..mins..":"..secs.."+"..days -end -end -function ARTY:_ClockToSeconds(clock) -self:F3({clock=clock}) -if clock==nil then -return nil -end -local seconds=0 -local dsplit=self:_split(clock,"+") -if#dsplit>1 then -seconds=seconds+tonumber(dsplit[2])*60*60*24 -end -local tsplit=self:_split(dsplit[1],":") -local i=1 -for _,time in ipairs(tsplit)do -if i==1 then -seconds=seconds+tonumber(time)*60*60 -elseif i==2 then -seconds=seconds+tonumber(time)*60 -elseif i==3 then -seconds=seconds+tonumber(time) -end -i=i+1 -end -self:T3(self.lid..string.format("Clock %s = %d seconds",clock,seconds)) -return seconds -end -SUPPRESSION={ -ClassName="SUPPRESSION", -Debug=false, -lid=nil, -flare=false, -smoke=false, -DCSdesc=nil, -Type=nil, -IsInfantry=nil, -SpeedMax=nil, -Tsuppress_ave=15, -Tsuppress_min=5, -Tsuppress_max=25, -TsuppressOver=nil, -IniGroupStrength=nil, -Nhit=0, -Formation="Off road", -Speed=4, -MenuON=false, -FallbackON=false, -FallbackWait=60, -FallbackDist=100, -FallbackHeading=nil, -TakecoverON=false, -TakecoverWait=120, -TakecoverRange=300, -hideout=nil, -PminFlee=10, -PmaxFlee=90, -RetreatZone=nil, -RetreatDamage=nil, -RetreatWait=7200, -CurrentAlarmState="unknown", -CurrentROE="unknown", -DefaultAlarmState="Auto", -DefaultROE="Weapon Free", -eventmoose=true, -waypoints={}, -} -SUPPRESSION.ROE={ -Hold="Weapon Hold", -Free="Weapon Free", -Return="Return Fire", -} -SUPPRESSION.AlarmState={ -Auto="Auto", -Green="Green", -Red="Red", -} -SUPPRESSION.MenuF10=nil -SUPPRESSION.version="0.9.4" -function SUPPRESSION:New(group) -local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) -if group then -self.lid=string.format("SUPPRESSION %s | ",tostring(group:GetName())) -self:T(self.lid..string.format("SUPPRESSION version %s. Activating suppressive fire for group %s",SUPPRESSION.version,group:GetName())) -else -self:E("SUPPRESSION | Requested group does not exist! (Has to be a MOOSE group)") -return nil -end -if group:IsGround()==false then -self:E(self.lid..string.format("SUPPRESSION fire group %s has to be a GROUND group!",group:GetName())) -return nil -end -self:SetControllable(group) -self.DCSdesc=group:GetDCSDesc(1) -self.SpeedMax=group:GetSpeedMax() -self.Speed=self.SpeedMax -self.IsInfantry=group:GetUnit(1):HasAttribute("Infantry") -self.Type=group:GetTypeName() -self.IniGroupStrength=#group:GetUnits() -self:SetDefaultROE("Free") -self:SetDefaultAlarmState("Auto") -self:AddTransition("*","Start","CombatReady") -self:AddTransition("*","Status","*") -self:AddTransition("CombatReady","Hit","Suppressed") -self:AddTransition("Suppressed","Hit","Suppressed") -self:AddTransition("Suppressed","Recovered","CombatReady") -self:AddTransition("Suppressed","TakeCover","TakingCover") -self:AddTransition("Suppressed","FallBack","FallingBack") -self:AddTransition("*","Retreat","Retreating") -self:AddTransition("TakingCover","FightBack","CombatReady") -self:AddTransition("FallingBack","FightBack","CombatReady") -self:AddTransition("Retreating","Retreated","Retreated") -self:AddTransition("*","OutOfAmmo","*") -self:AddTransition("*","Dead","*") -self:AddTransition("*","Stop","Stopped") -self:AddTransition("TakingCover","Hit","TakingCover") -self:AddTransition("FallingBack","Hit","FallingBack") -return self -end -function SUPPRESSION:SetSuppressionTime(Tave,Tmin,Tmax) -self:F({Tave=Tave,Tmin=Tmin,Tmax=Tmax}) -self.Tsuppress_min=Tmin or self.Tsuppress_min -self.Tsuppress_min=math.max(self.Tsuppress_min,1) -self.Tsuppress_max=Tmax or self.Tsuppress_max -self.Tsuppress_max=math.max(self.Tsuppress_max,self.Tsuppress_min) -self.Tsuppress_ave=Tave or self.Tsuppress_ave -self.Tsuppress_ave=math.max(self.Tsuppress_min) -self.Tsuppress_ave=math.min(self.Tsuppress_max) -self:T(self.lid..string.format("Set ave suppression time to %d seconds.",self.Tsuppress_ave)) -self:T(self.lid..string.format("Set min suppression time to %d seconds.",self.Tsuppress_min)) -self:T(self.lid..string.format("Set max suppression time to %d seconds.",self.Tsuppress_max)) -end -function SUPPRESSION:SetRetreatZone(zone) -self:F({zone=zone}) -self.RetreatZone=zone -end -function SUPPRESSION:DebugOn() -self:F() -self.Debug=true -end -function SUPPRESSION:FlareOn() -self:F() -self.flare=true -end -function SUPPRESSION:SmokeOn() -self:F() -self.smoke=true -end -function SUPPRESSION:SetFormation(formation) -self:F(formation) -self.Formation=formation or"Vee" -end -function SUPPRESSION:SetSpeed(speed) -self:F(speed) -self.Speed=speed or self.SpeedMax -self.Speed=math.min(self.Speed,self.SpeedMax) -end -function SUPPRESSION:Fallback(switch) -self:F(switch) -if switch==nil then -switch=true -end -self.FallbackON=switch -end -function SUPPRESSION:SetFallbackDistance(distance) -self:F(distance) -self.FallbackDist=distance -end -function SUPPRESSION:SetFallbackWait(time) -self:F(time) -self.FallbackWait=time -end -function SUPPRESSION:Takecover(switch) -self:F(switch) -if switch==nil then -switch=true -end -self.TakecoverON=switch -end -function SUPPRESSION:SetTakecoverWait(time) -self:F(time) -self.TakecoverWait=time -end -function SUPPRESSION:SetTakecoverRange(range) -self:F(range) -self.TakecoverRange=range -end -function SUPPRESSION:SetTakecoverPlace(Hideout) -self.hideout=Hideout -end -function SUPPRESSION:SetMinimumFleeProbability(probability) -self:F(probability) -self.PminFlee=probability or 10 -end -function SUPPRESSION:SetMaximumFleeProbability(probability) -self:F(probability) -self.PmaxFlee=probability or 90 -end -function SUPPRESSION:SetRetreatDamage(damage) -self:F(damage) -self.RetreatDamage=damage or 50 -end -function SUPPRESSION:SetRetreatWait(time) -self:F(time) -self.RetreatWait=time or 7200 -end -function SUPPRESSION:SetDefaultAlarmState(alarmstate) -self:F(alarmstate) -if alarmstate:lower()=="auto"then -self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto -elseif alarmstate:lower()=="green"then -self.DefaultAlarmState=SUPPRESSION.AlarmState.Green -elseif alarmstate:lower()=="red"then -self.DefaultAlarmState=SUPPRESSION.AlarmState.Red -else -self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto -end -end -function SUPPRESSION:SetDefaultROE(roe) -self:F(roe) -if roe:lower()=="free"then -self.DefaultROE=SUPPRESSION.ROE.Free -elseif roe:lower()=="hold"then -self.DefaultROE=SUPPRESSION.ROE.Hold -elseif roe:lower()=="return"then -self.DefaultROE=SUPPRESSION.ROE.Return -else -self.DefaultROE=SUPPRESSION.ROE.Free -end -end -function SUPPRESSION:MenuOn(switch) -self:F(switch) -if switch==nil then -switch=true -end -self.MenuON=switch -end -function SUPPRESSION:_CreateMenuGroup() -local SubMenuName=self.Controllable:GetName() -local MenuGroup=MENU_MISSION:New(SubMenuName,SUPPRESSION.MenuF10) -MENU_MISSION_COMMAND:New("Fallback!",MenuGroup,self.OrderFallBack,self) -MENU_MISSION_COMMAND:New("Take Cover!",MenuGroup,self.OrderTakeCover,self) -MENU_MISSION_COMMAND:New("Retreat!",MenuGroup,self.OrderRetreat,self) -MENU_MISSION_COMMAND:New("Report Status",MenuGroup,self.Status,self,true) -end -function SUPPRESSION:OrderFallBack() -local group=self.Controllable -local vicinity=group:GetCoordinate():GetRandomVec2InRadius(150,100) -local coord=COORDINATE:NewFromVec2(vicinity) -self:FallBack(self.Controllable) -end -function SUPPRESSION:OrderTakeCover() -local Hideout=self.hideout -if self.hideout==nil then -Hideout=self:_SearchHideout() -end -self:TakeCover(Hideout) -end -function SUPPRESSION:OrderRetreat() -self:Retreat() -end -function SUPPRESSION:StatusReport(message) -local group=self.Controllable -local nunits=group:CountAliveUnits() -local roe=self.CurrentROE -local state=self.CurrentAlarmState -local life_min,life_max,life_ave,life_ave0,groupstrength=self:_GetLife() -local ammotot=group:GetAmmunition() -local detectedG=group:GetDetectedGroupSet():CountAlive() -local detectedU=group:GetDetectedUnitSet():Count() -local text=string.format("State %s, Units=%d/%d, ROE=%s, AlarmState=%s, Hits=%d, Life(min/max/ave/ave0)=%d/%d/%d/%d, Total Ammo=%d, Detected=%d/%d", -self:GetState(),nunits,self.IniGroupStrength,self.CurrentROE,self.CurrentAlarmState,self.Nhit,life_min,life_max,life_ave,life_ave0,ammotot,detectedG,detectedU) -MESSAGE:New(text,10):ToAllIf(message or self.Debug) -self:I(self.lid..text) -end -function SUPPRESSION:onafterStart(Controllable,From,Event,To) -self:_EventFromTo("onafterStart",Event,From,To) -local text=string.format("Started SUPPRESSION for group %s.",Controllable:GetName()) -self:I(self.lid..text) -MESSAGE:New(text,10):ToAllIf(self.Debug) -local rzone="not defined" -if self.RetreatZone then -rzone=self.RetreatZone:GetName() -end -if self.RetreatDamage==nil then -if self.RetreatZone then -if self.IniGroupStrength==1 then -self.RetreatDamage=60.0 -elseif self.IniGroupStrength==2 then -self.RetreatDamage=50.0 -else -self.RetreatDamage=66.5 -end -else -self.RetreatDamage=100 -end -end -if self.MenuON then -if not SUPPRESSION.MenuF10 then -SUPPRESSION.MenuF10=MENU_MISSION:New("Suppression") -end -self:_CreateMenuGroup() -end -self:_SetAlarmState(self.DefaultAlarmState) -self:_SetROE(self.DefaultROE) -local text=string.format("\n******************************************************\n") -text=text..string.format("Suppressed group = %s\n",Controllable:GetName()) -text=text..string.format("Type = %s\n",self.Type) -text=text..string.format("IsInfantry = %s\n",tostring(self.IsInfantry)) -text=text..string.format("Group strength = %d\n",self.IniGroupStrength) -text=text..string.format("Average time = %5.1f seconds\n",self.Tsuppress_ave) -text=text..string.format("Minimum time = %5.1f seconds\n",self.Tsuppress_min) -text=text..string.format("Maximum time = %5.1f seconds\n",self.Tsuppress_max) -text=text..string.format("Default ROE = %s\n",self.DefaultROE) -text=text..string.format("Default AlarmState = %s\n",self.DefaultAlarmState) -text=text..string.format("Fall back ON = %s\n",tostring(self.FallbackON)) -text=text..string.format("Fall back distance = %5.1f m\n",self.FallbackDist) -text=text..string.format("Fall back wait = %5.1f seconds\n",self.FallbackWait) -text=text..string.format("Fall back heading = %s degrees\n",tostring(self.FallbackHeading)) -text=text..string.format("Take cover ON = %s\n",tostring(self.TakecoverON)) -text=text..string.format("Take cover search = %5.1f m\n",self.TakecoverRange) -text=text..string.format("Take cover wait = %5.1f seconds\n",self.TakecoverWait) -text=text..string.format("Min flee probability = %5.1f\n",self.PminFlee) -text=text..string.format("Max flee probability = %5.1f\n",self.PmaxFlee) -text=text..string.format("Retreat zone = %s\n",rzone) -text=text..string.format("Retreat damage = %5.1f %%\n",self.RetreatDamage) -text=text..string.format("Retreat wait = %5.1f seconds\n",self.RetreatWait) -text=text..string.format("Speed = %5.1f km/h\n",self.Speed) -text=text..string.format("Speed max = %5.1f km/h\n",self.SpeedMax) -text=text..string.format("Formation = %s\n",self.Formation) -text=text..string.format("******************************************************\n") -self:T(self.lid..text) -if self.eventmoose then -self:HandleEvent(EVENTS.Hit,self._OnEventHit) -self:HandleEvent(EVENTS.Dead,self._OnEventDead) -else -world.addEventHandler(self) -end -self:__Status(-1) -end -function SUPPRESSION:onafterStatus(Controllable,From,Event,To) -local group=self.Controllable -if group then -local nunits=group:CountAliveUnits() -if nunits>0 then -local nammo=group:GetAmmunition() -if nammo==0 then -self:OutOfAmmo() -end -self:StatusReport(false) -if self:GetState()~="Stopped"then -self:__Status(-30) -end -else -self:Stop() -end -else -self:Stop() -end -end -function SUPPRESSION:onafterHit(Controllable,From,Event,To,Unit,AttackUnit) -self:_EventFromTo("onafterHit",Event,From,To) -if From=="CombatReady"or From=="Suppressed"then -self:_Suppress() -end -local life_min,life_max,life_ave,life_ave0,groupstrength=self:_GetLife() -local Damage=100-life_ave0 -local RetreatCondition=Damage>=self.RetreatDamage-0.01 and self.RetreatZone -local Pflee=(self.PmaxFlee-self.PminFlee)/self.RetreatDamage*math.min(Damage,self.RetreatDamage)+self.PminFlee -local P=math.random(0,100) -local FleeCondition=P Prand ==> Flee)\n",Controllable:GetName(),Pflee,P) -self:T(self.lid..text) -if Damage>=99.9 then -return -end -if RetreatCondition then -self:Retreat() -elseif FleeCondition then -if self.FallbackON and AttackUnit:IsGround()then -self:FallBack(AttackUnit) -elseif self.TakecoverON then -local Hideout=self.hideout -if self.hideout==nil then -Hideout=self:_SearchHideout() -end -self:TakeCover(Hideout) -end -end -end -function SUPPRESSION:onbeforeRecovered(Controllable,From,Event,To) -self:_EventFromTo("onbeforeRecovered",Event,From,To) -local Tnow=timer.getTime() -self:T(self.lid..string.format("onbeforeRecovered: Time now: %d - Time over: %d",Tnow,self.TsuppressionOver)) -if Tnow>=self.TsuppressionOver then -return true -else -return false -end -end -function SUPPRESSION:onafterRecovered(Controllable,From,Event,To) -self:_EventFromTo("onafterRecovered",Event,From,To) -if Controllable and Controllable:IsAlive()then -local text=string.format("Group %s has recovered!",Controllable:GetName()) -MESSAGE:New(text,10):ToAllIf(self.Debug) -self:T(self.lid..text) -self:_SetROE() -if self.flare or self.Debug then -Controllable:FlareGreen() -end -end -end -function SUPPRESSION:onafterFightBack(Controllable,From,Event,To) -self:_EventFromTo("onafterFightBack",Event,From,To) -self:_SetROE() -self:_SetAlarmState() -local group=Controllable -local Waypoints=group:GetTemplateRoutePoints() -group:Route(Waypoints,5) -end -function SUPPRESSION:onbeforeFallBack(Controllable,From,Event,To,AttackUnit) -self:_EventFromTo("onbeforeFallBack",Event,From,To) -if From=="FallingBack"then -return false -else -return true -end -end -function SUPPRESSION:onafterFallBack(Controllable,From,Event,To,AttackUnit) -self:_EventFromTo("onafterFallback",Event,From,To) -self:T(self.lid..string.format("Group %s is falling back after %d hits.",Controllable:GetName(),self.Nhit)) -local ACoord=AttackUnit:GetCoordinate() -local DCoord=Controllable:GetCoordinate() -local heading=self:_Heading(ACoord,DCoord) -if self.FallbackHeading then -heading=self.FallbackHeading -end -local Coord=DCoord:Translate(self.FallbackDist,heading) -if self.Debug then -local MarkerID=Coord:MarkToAll("Fall back position for group "..Controllable:GetName()) -end -if self.smoke or self.Debug then -Coord:SmokeBlue() -end -self:_SetROE(SUPPRESSION.ROE.Hold) -self:_SetAlarmState(SUPPRESSION.AlarmState.Auto) -self:_Run(Coord,self.Speed,self.Formation,self.FallbackWait) -end -function SUPPRESSION:onbeforeTakeCover(Controllable,From,Event,To,Hideout) -self:_EventFromTo("onbeforeTakeCover",Event,From,To) -if From=="TakingCover"then -return false -end -if Hideout~=nil then -return true -else -return false -end -end -function SUPPRESSION:onafterTakeCover(Controllable,From,Event,To,Hideout) -self:_EventFromTo("onafterTakeCover",Event,From,To) -if self.Debug then -local MarkerID=Hideout:MarkToAll(string.format("Hideout for group %s",Controllable:GetName())) -end -if self.smoke or self.Debug then -Hideout:SmokeBlue() -end -self:_SetROE(SUPPRESSION.ROE.Hold) -self:_SetAlarmState(SUPPRESSION.AlarmState.Green) -self:_Run(Hideout,self.Speed,self.Formation,self.TakecoverWait) -end -function SUPPRESSION:onafterOutOfAmmo(Controllable,From,Event,To) -self:_EventFromTo("onafterOutOfAmmo",Event,From,To) -self:I(self.lid..string.format("Out of ammo!")) -if self.RetreatZone then -self:Retreat() -end -end -function SUPPRESSION:onbeforeRetreat(Controllable,From,Event,To) -self:_EventFromTo("onbeforeRetreat",Event,From,To) -if From=="Retreating"then -local text=string.format("Group %s is already retreating.",tostring(Controllable:GetName())) -self:T2(self.lid..text) -return false -else -return true -end -end -function SUPPRESSION:onafterRetreat(Controllable,From,Event,To) -self:_EventFromTo("onafterRetreat",Event,From,To) -local text=string.format("Group %s is retreating! Alarm state green.",Controllable:GetName()) -MESSAGE:New(text,10):ToAllIf(self.Debug) -self:T(self.lid..text) -local ZoneCoord=self.RetreatZone:GetRandomCoordinate() -local ZoneVec2=ZoneCoord:GetVec2() -if self.smoke or self.Debug then -ZoneCoord:SmokeBlue() -end -if self.Debug then -self.RetreatZone:SmokeZone(SMOKECOLOR.Red,12) -end -self:_SetROE(SUPPRESSION.ROE.Hold) -self:_SetAlarmState(SUPPRESSION.AlarmState.Green) -self:_Run(ZoneCoord,self.Speed,self.Formation,self.RetreatWait) -end -function SUPPRESSION:onbeforeRetreated(Controllable,From,Event,To) -self:_EventFromTo("onbeforeRetreated",Event,From,To) -local inzone=self.RetreatZone:IsVec3InZone(Controllable:GetVec3()) -return inzone -end -function SUPPRESSION:onafterRetreated(Controllable,From,Event,To) -self:_EventFromTo("onafterRetreated",Event,From,To) -self:_SetROE(SUPPRESSION.ROE.Return) -self:_SetAlarmState(SUPPRESSION.AlarmState.Auto) -end -function SUPPRESSION:onafterDead(Controllable,From,Event,To) -self:_EventFromTo("onafterDead",Event,From,To) -local group=self.Controllable -if group then -local nunits=group:CountAliveUnits() -local text=string.format("Group %s: One of our units just died! %d units left.",self.Controllable:GetName(),nunits) -MESSAGE:New(text,10):ToAllIf(self.Debug) -self:T(self.lid..text) -if nunits==0 then -self:Stop() -end -else -self:Stop() -end -end -function SUPPRESSION:onafterStop(Controllable,From,Event,To) -self:_EventFromTo("onafterStop",Event,From,To) -local text=string.format("Stopping SUPPRESSION for group %s",self.Controllable:GetName()) -MESSAGE:New(text,10):ToAllIf(self.Debug) -self:I(self.lid..text) -self.CallScheduler:Clear() -if self.mooseevents then -self:UnHandleEvent(EVENTS.Dead) -self:UnHandleEvent(EVENTS.Hit) -else -world.removeEventHandler(self) -end -end -function SUPPRESSION:onEvent(Event) -if Event==nil or Event.initiator==nil or Unit.getByName(Event.initiator:getName())==nil then -return true -end -local EventData={} -if Event.initiator then -EventData.IniDCSUnit=Event.initiator -EventData.IniUnitName=Event.initiator:getName() -EventData.IniDCSGroup=Event.initiator:getGroup() -EventData.IniGroupName=Event.initiator:getGroup():getName() -EventData.IniGroup=GROUP:FindByName(EventData.IniGroupName) -EventData.IniUnit=UNIT:FindByName(EventData.IniUnitName) -end -if Event.target then -EventData.TgtDCSUnit=Event.target -EventData.TgtUnitName=Event.target:getName() -EventData.TgtDCSGroup=Event.target:getGroup() -EventData.TgtGroupName=Event.target:getGroup():getName() -EventData.TgtGroup=GROUP:FindByName(EventData.TgtGroupName) -EventData.TgtUnit=UNIT:FindByName(EventData.TgtUnitName) -end -if Event.id==world.event.S_EVENT_HIT then -self:_OnEventHit(EventData) -end -if Event.id==world.event.S_EVENT_DEAD then -self:_OnEventDead(EventData) -end -end -function SUPPRESSION:_OnEventHit(EventData) -self:F3(EventData) -local GroupNameSelf=self.Controllable:GetName() -local GroupNameTgt=EventData.TgtGroupName -local TgtUnit=EventData.TgtUnit -local tgt=EventData.TgtDCSUnit -local IniUnit=EventData.IniUnit -if GroupNameTgt==GroupNameSelf then -self:T(self.lid..string.format("Hit event at t = %5.1f",timer.getTime())) -if self.flare or self.Debug then -TgtUnit:FlareRed() -end -self.Nhit=self.Nhit+1 -self:T(self.lid..string.format("Group %s has just been hit %d times.",self.Controllable:GetName(),self.Nhit)) -local life=tgt:getLife()/(tgt:getLife0()+1)*100 -self:T2(self.lid..string.format("Target unit life = %5.1f",life)) -self:__Hit(3,TgtUnit,IniUnit) -end -end -function SUPPRESSION:_OnEventDead(EventData) -local GroupNameSelf=self.Controllable:GetName() -local GroupNameIni=EventData.IniGroupName -if GroupNameIni==GroupNameSelf then -local IniUnit=EventData.IniUnit -local IniUnitName=EventData.IniUnitName -if EventData.IniUnit then -self:T2(self.lid..string.format("Group %s: Dead MOOSE unit DOES exist! Unit name %s.",GroupNameIni,IniUnitName)) -else -self:T2(self.lid..string.format("Group %s: Dead MOOSE unit DOES NOT not exist! Unit name %s.",GroupNameIni,IniUnitName)) -end -if EventData.IniDCSUnit then -self:T2(self.lid..string.format("Group %s: Dead DCS unit DOES exist! Unit name %s.",GroupNameIni,IniUnitName)) -else -self:T2(self.lid..string.format("Group %s: Dead DCS unit DOES NOT exist! Unit name %s.",GroupNameIni,IniUnitName)) -end -if IniUnit and(self.flare or self.Debug)then -IniUnit:FlareWhite() -self:T(self.lid..string.format("Flare Dead MOOSE unit.")) -end -if EventData.IniDCSUnit and(self.flare or self.Debug)then -local p=EventData.IniDCSUnit:getPosition().p -trigger.action.signalFlare(p,trigger.flareColor.Yellow,0) -self:T(self.lid..string.format("Flare Dead DCS unit.")) -end -self:Status() -self:__Dead(0.1) -end -end -function SUPPRESSION:_Suppress() -local Tnow=timer.getTime() -local Controllable=self.Controllable -self:_SetROE(SUPPRESSION.ROE.Hold) -local sigma=(self.Tsuppress_max-self.Tsuppress_min)/4 -local Tsuppress=self:_Random_Gaussian(self.Tsuppress_ave,sigma,self.Tsuppress_min,self.Tsuppress_max) -local renew=true -if self.TsuppressionOver~=nil then -if Tsuppress+Tnow>self.TsuppressionOver then -self.TsuppressionOver=Tnow+Tsuppress -else -renew=false -end -else -self.TsuppressionOver=Tnow+Tsuppress -end -if renew then -self:__Recovered(self.TsuppressionOver-Tnow) -end -local text=string.format("Group %s is suppressed for %d seconds. Suppression ends at %d:%02d.",Controllable:GetName(),Tsuppress,self.TsuppressionOver/60,self.TsuppressionOver%60) -MESSAGE:New(text,10):ToAllIf(self.Debug) -self:T(self.lid..text) -end -function SUPPRESSION:_Run(fin,speed,formation,wait) -speed=speed or 20 -formation=formation or ENUMS.Formation.Vehicle.OffRoad -wait=wait or 30 -local group=self.Controllable -if group and group:IsAlive()then -local ini=group:GetCoordinate() -local dist=ini:Get2DDistance(fin) -local heading=self:_Heading(ini,fin) -local wp={} -local tasks={} -wp[1]=ini:WaypointGround(speed,formation) -if self.Debug then -local MarkerID=ini:MarkToAll(string.format("Waypoing %d of group %s (initial)",#wp,self.Controllable:GetName())) -end -local ConditionWait=group:TaskCondition(nil,nil,nil,nil,wait,nil) -local TaskHold=group:TaskHold() -local TaskComboFin={} -TaskComboFin[#TaskComboFin+1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint",self,#wp,true) -TaskComboFin[#TaskComboFin+1]=group:TaskControlled(TaskHold,ConditionWait) -wp[#wp+1]=fin:WaypointGround(speed,formation,TaskComboFin) -if self.Debug then -local MarkerID=fin:MarkToAll(string.format("Waypoing %d of group %s (final)",#wp,self.Controllable:GetName())) -end -group:Route(wp) -else -self:E(self.lid..string.format("ERROR: Group is not alive!")) -end -end -function SUPPRESSION._Passing_Waypoint(group,Fsm,i,final) -local text=string.format("Group %s passing waypoint %d (final=%s)",group:GetName(),i,tostring(final)) -MESSAGE:New(text,10):ToAllIf(Fsm.Debug) -if Fsm.Debug then -env.info(Fsm.lid..text) -end -if final then -if Fsm:is("Retreating")then -Fsm:Retreated() -else -Fsm:FightBack() -end -end -end -function SUPPRESSION:_SearchHideout() -local Zone=ZONE_GROUP:New("Zone_Hiding",self.Controllable,self.TakecoverRange) -local gpos=self.Controllable:GetCoordinate() -Zone:Scan(Object.Category.SCENERY) -local hideouts={} -for SceneryTypeName,SceneryData in pairs(Zone:GetScannedScenery())do -for SceneryName,SceneryObject in pairs(SceneryData)do -local SceneryObject=SceneryObject -local spos=SceneryObject:GetCoordinate() -local distance=spos:Get2DDistance(gpos) -if self.Debug then -local MarkerID=SceneryObject:GetCoordinate():MarkToAll(string.format("%s scenery object %s",self.Controllable:GetName(),SceneryObject:GetTypeName())) -local text=string.format("%s scenery: %s, Coord %s",self.Controllable:GetName(),SceneryObject:GetTypeName(),SceneryObject:GetCoordinate():ToStringLLDMS()) -self:T2(self.lid..text) -end -table.insert(hideouts,{object=SceneryObject,distance=distance}) -end -end -local Hideout=nil -if#hideouts>0 then -self:T(self.lid.."Number of hideouts "..#hideouts) -local _sort=function(a,b)return a.distancelife_max then -life_max=life -end -life_ave=life_ave+life -if self.Debug then -local text=string.format("n=%02d: Life = %3.1f, Life0 = %3.1f, min=%3.1f, max=%3.1f, ave=%3.1f, group=%3.1f",n,unit:GetLife(),unit:GetLife0(),life_min,life_max,life_ave/n,groupstrength) -self:T2(self.lid..text) -end -end -end -if n==0 then -return 0,0,0,0,0 -end -life_ave0=life_ave/self.IniGroupStrength -life_ave=life_ave/n -return life_min,life_max,life_ave,life_ave0,groupstrength -else -return 0,0,0,0,0 -end -end -function SUPPRESSION:_Heading(a,b) -local dx=b.x-a.x -local dy=b.z-a.z -local angle=math.deg(math.atan2(dy,dx)) -if angle<0 then -angle=360+angle -end -return angle -end -function SUPPRESSION:_Random_Gaussian(x0,sigma,xmin,xmax) -sigma=sigma or 5 -local r -local gotit=false -local i=0 -while not gotit do -local x1=math.random() -local x2=math.random() -r=math.sqrt(-2*sigma*sigma*math.log(x1))*math.cos(2*math.pi*x2)+x0 -i=i+1 -if(r>=xmin and r<=xmax)or i>100 then -gotit=true -end -end -return r -end -function SUPPRESSION:_SetROE(roe) -local group=self.Controllable -roe=roe or self.DefaultROE -self.CurrentROE=roe -if roe==SUPPRESSION.ROE.Free then -group:OptionROEOpenFire() -elseif roe==SUPPRESSION.ROE.Hold then -group:OptionROEHoldFire() -elseif roe==SUPPRESSION.ROE.Return then -group:OptionROEReturnFire() -else -self:E(self.lid.."Unknown ROE requested: "..tostring(roe)) -group:OptionROEOpenFire() -self.CurrentROE=SUPPRESSION.ROE.Free -end -local text=string.format("Group %s now has ROE %s.",self.Controllable:GetName(),self.CurrentROE) -self:T(self.lid..text) -end -function SUPPRESSION:_SetAlarmState(state) -local group=self.Controllable -state=state or self.DefaultAlarmState -self.CurrentAlarmState=state -if state==SUPPRESSION.AlarmState.Auto then -group:OptionAlarmStateAuto() -elseif state==SUPPRESSION.AlarmState.Green then -group:OptionAlarmStateGreen() -elseif state==SUPPRESSION.AlarmState.Red then -group:OptionAlarmStateRed() -else -self:E(self.lid.."Unknown alarm state requested: "..tostring(state)) -group:OptionAlarmStateAuto() -self.CurrentAlarmState=SUPPRESSION.AlarmState.Auto -end -local text=string.format("Group %s now has Alarm State %s.",self.Controllable:GetName(),self.CurrentAlarmState) -self:T(self.lid..text) -end -function SUPPRESSION:_EventFromTo(BA,Event,From,To) -local text=string.format("\n%s: %s EVENT %s: %s --> %s",BA,self.Controllable:GetName(),Event,From,To) -self:T2(self.lid..text) -end -PSEUDOATC={ -ClassName="PSEUDOATC", -group={}, -Debug=false, -mdur=30, -mrefresh=120, -talt=3, -chatty=true, -eventsmoose=true, -reportplayername=false, -} -PSEUDOATC.id="PseudoATC | " -PSEUDOATC.version="0.10.5" -function PSEUDOATC:New() -local self=BASE:Inherit(self,BASE:New()) -self:E(PSEUDOATC.id..string.format("PseudoATC version %s",PSEUDOATC.version)) -return self -end -function PSEUDOATC:Start() -self:F() -self:I(PSEUDOATC.id.."Starting PseudoATC") -self:HandleEvent(EVENTS.Birth,self._OnBirth) -self:HandleEvent(EVENTS.Land,self._PlayerLanded) -self:HandleEvent(EVENTS.Takeoff,self._PlayerTakeOff) -self:HandleEvent(EVENTS.PlayerLeaveUnit,self._PlayerLeft) -self:HandleEvent(EVENTS.Crash,self._PlayerLeft) -end -function PSEUDOATC:DebugOn() -self.Debug=true -end -function PSEUDOATC:DebugOff() -self.Debug=false -end -function PSEUDOATC:ChattyOn() -self.chatty=true -end -function PSEUDOATC:ChattyOff() -self.chatty=false -end -function PSEUDOATC:SetMessageDuration(duration) -self.mdur=duration or 30 -end -function PSEUDOATC:SetReportPlayername() -self.reportplayername=true -return self -end -function PSEUDOATC:SetMenuRefresh(interval) -self.mrefresh=interval or 120 -end -function PSEUDOATC:SetEventsMoose(switch) -self.eventsmoose=switch -end -function PSEUDOATC:SetReportAltInterval(interval) -self.talt=interval or 3 -end -function PSEUDOATC:_OnBirth(EventData) -self:F({EventData=EventData}) -local _unitName=EventData.IniUnitName -local _unit=EventData.IniUnit -local _playername=EventData.IniPlayerName -if _unit and _playername then -self:PlayerEntered(_unit) -end -end -function PSEUDOATC:_PlayerLeft(EventData) -self:F({EventData=EventData}) -local _unitName=EventData.IniUnitName -local _unit=EventData.IniUnit -local _playername=EventData.IniPlayerName -if _unit and _playername then -self:PlayerLeft(_unit) -end -end -function PSEUDOATC:_PlayerLanded(EventData) -self:F({EventData=EventData}) -local _unitName=EventData.IniUnitName -local _unit=EventData.IniUnit -local _playername=EventData.IniPlayerName -local _base=nil -local _baseName=nil -if EventData.place then -_base=EventData.place -_baseName=EventData.place:getName() -end -if _unit and _playername and _base then -self:PlayerLanded(_unit,_baseName) -end -end -function PSEUDOATC:_PlayerTakeOff(EventData) -self:F({EventData=EventData}) -local _unitName=EventData.IniUnitName -local _unit=EventData.IniUnit -local _playername=EventData.IniPlayerName -local _base=nil -local _baseName=nil -if EventData.place then -_base=EventData.place -_baseName=EventData.place:getName() -end -if _unit and _playername and _base then -self:PlayerTakeOff(_unit,_baseName) -end -end -function PSEUDOATC:PlayerEntered(unit) -self:F2({unit=unit}) -local group=unit:GetGroup() -local GID=group:GetID() -local GroupName=group:GetName() -local PlayerName=unit:GetPlayerName() -local UnitName=unit:GetName() -local CallSign=unit:GetCallsign() -local UID=unit:GetDCSObject():getID() -if not self.group[GID]then -self.group[GID]={} -self.group[GID].player={} -end -self.group[GID].player[UID]={} -self.group[GID].player[UID].group=group -self.group[GID].player[UID].unit=unit -self.group[GID].player[UID].groupname=GroupName -self.group[GID].player[UID].unitname=UnitName -self.group[GID].player[UID].playername=PlayerName -self.group[GID].player[UID].callsign=CallSign -self.group[GID].player[UID].waypoints=group:GetTaskRoute() -local text=string.format("Player %s entered unit %s of group %s (id=%d).",PlayerName,UnitName,GroupName,GID) -self:T(PSEUDOATC.id..text) -MESSAGE:New(text,30):ToAllIf(self.Debug) -local countPlayerInGroup=0 -for _ in pairs(self.group[GID].player)do countPlayerInGroup=countPlayerInGroup+1 end -if countPlayerInGroup<=1 then -self.group[GID].menu_main=missionCommands.addSubMenuForGroup(GID,"Pseudo ATC") -end -self:MenuCreatePlayer(GID,UID) -self:LocalAirports(GID,UID) -self:MenuAirports(GID,UID) -self:MenuWaypoints(GID,UID) -self.group[GID].player[UID].scheduler,self.group[GID].player[UID].schedulerid=SCHEDULER:New(nil,self.MenuRefresh,{self,GID,UID},self.mrefresh,self.mrefresh) -end -function PSEUDOATC:PlayerLanded(unit,place) -self:F2({unit=unit,place=place}) -local group=unit:GetGroup() -local GID=group:GetID() -local UID=unit:GetDCSObject():getID() -local PlayerName=unit:GetPlayerName()or"Ghost" -local UnitName=unit:GetName()or"Ghostplane" -local GroupName=group:GetName()or"Ghostgroup" -if self.Debug then -local text=string.format("Player %s in unit %s of group %s landed at %s.",PlayerName,UnitName,GroupName,place) -self:T(PSEUDOATC.id..text) -MESSAGE:New(text,30):ToAllIf(self.Debug) -end -self:AltitudeTimerStop(GID,UID) -if place and self.chatty then -local text=string.format("Touchdown! Welcome to %s pilot %s. Have a nice day!",place,PlayerName) -MESSAGE:New(text,self.mdur):ToGroup(group) -end -end -function PSEUDOATC:PlayerTakeOff(unit,place) -self:F2({unit=unit,place=place}) -local group=unit:GetGroup() -local PlayerName=unit:GetPlayerName()or"Ghost" -local UnitName=unit:GetName()or"Ghostplane" -local GroupName=group:GetName()or"Ghostgroup" -local CallSign=unit:GetCallsign()or"Ghost11" -if self.Debug then -local text=string.format("Player %s in unit %s of group %s took off at %s.",PlayerName,UnitName,GroupName,place) -self:T(PSEUDOATC.id..text) -MESSAGE:New(text,30):ToAllIf(self.Debug) -end -if place and self.chatty then -local text=string.format("%s, %s, you are airborne. Have a safe trip!",place,CallSign) -if self.reportplayername then -text=string.format("%s, %s, you are airborne. Have a safe trip!",place,PlayerName) -end -MESSAGE:New(text,self.mdur):ToGroup(group) -end -end -function PSEUDOATC:PlayerLeft(unit) -self:F({unit=unit}) -local group=unit:GetGroup() -local GID=group:GetID() -local UID=unit:GetDCSObject():getID() -if self.group[GID]and self.group[GID].player and self.group[GID].player[UID]then -local PlayerName=self.group[GID].player[UID].playername -local CallSign=self.group[GID].player[UID].callsign -local UnitName=self.group[GID].player[UID].unitname -local GroupName=self.group[GID].player[UID].groupname -local text=string.format("Player %s (callsign %s) of group %s just left unit %s.",PlayerName,CallSign,GroupName,UnitName) -self:T(PSEUDOATC.id..text) -MESSAGE:New(text,30):ToAllIf(self.Debug) -if self.group[GID].player[UID].schedulerid then -self.group[GID].player[UID].scheduler:Stop(self.group[GID].player[UID].schedulerid) -end -self:AltitudeTimerStop(GID,UID) -if self.group[GID].player[UID].menu_own then -missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_own) -end -local countPlayerInGroup=0 -for _ in pairs(self.group[GID].player)do countPlayerInGroup=countPlayerInGroup+1 end -if self.group[GID].menu_main and countPlayerInGroup==1 then -missionCommands.removeItemForGroup(GID,self.group[GID].menu_main) -end -self.group[GID].player[UID]=nil -end -end -function PSEUDOATC:MenuRefresh(GID,UID) -self:F({GID=GID,UID=UID}) -local text=string.format("Refreshing menues for player %s in group %s.",self.group[GID].player[UID].playername,self.group[GID].player[UID].groupname) -self:T(PSEUDOATC.id..text) -MESSAGE:New(text,30):ToAllIf(self.Debug) -self:MenuClear(GID,UID) -self:LocalAirports(GID,UID) -self:MenuAirports(GID,UID) -self:MenuWaypoints(GID,UID) -end -function PSEUDOATC:MenuCreatePlayer(GID,UID) -self:F({GID=GID,UID=UID}) -local PlayerName=self.group[GID].player[UID].playername -self.group[GID].player[UID].menu_own=missionCommands.addSubMenuForGroup(GID,PlayerName,self.group[GID].menu_main) -end -function PSEUDOATC:MenuClear(GID,UID) -self:F({GID=GID,UID=UID}) -local text=string.format("Clearing menus for player %s in group %s.",self.group[GID].player[UID].playername,self.group[GID].player[UID].groupname) -self:T(PSEUDOATC.id..text) -MESSAGE:New(text,30):ToAllIf(self.Debug) -if self.group[GID].player[UID].menu_airports then -missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_airports) -self.group[GID].player[UID].menu_airports=nil -else -self:T2(PSEUDOATC.id.."No airports to clear menus.") -end -if self.group[GID].player[UID].menu_waypoints then -missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_waypoints) -self.group[GID].player[UID].menu_waypoints=nil -end -if self.group[GID].player[UID].menu_reportalt then -missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_reportalt) -self.group[GID].player[UID].menu_reportalt=nil -end -if self.group[GID].player[UID].menu_requestalt then -missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_requestalt) -self.group[GID].player[UID].menu_requestalt=nil -end -end -function PSEUDOATC:MenuAirports(GID,UID) -self:F({GID=GID,UID=UID}) -self.group[GID].player[UID].menu_airports=missionCommands.addSubMenuForGroup(GID,"Local Airports",self.group[GID].player[UID].menu_own) -local i=0 -for _,airport in pairs(self.group[GID].player[UID].airports)do -i=i+1 -if i>10 then -break -end -local name=airport.name -local d=airport.distance -local pos=AIRBASE:FindByName(name):GetCoordinate() -local submenu=missionCommands.addSubMenuForGroup(GID,name,self.group[GID].player[UID].menu_airports) -missionCommands.addCommandForGroup(GID,"Weather Report",submenu,self.ReportWeather,self,GID,UID,pos,name) -missionCommands.addCommandForGroup(GID,"Request BR",submenu,self.ReportBR,self,GID,UID,pos,name) -self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d",name,GID)) -end -end -function PSEUDOATC:MenuWaypoints(GID,UID) -self:F({GID=GID,UID=UID}) -local callsign=self.group[GID].player[UID].callsign -self:T(PSEUDOATC.id..string.format("Creating waypoint menu for %s (ID %d).",callsign,GID)) -if#self.group[GID].player[UID].waypoints>0 then -self.group[GID].player[UID].menu_waypoints=missionCommands.addSubMenuForGroup(GID,"Waypoints",self.group[GID].player[UID].menu_own) -local j=0 -for i,wp in pairs(self.group[GID].player[UID].waypoints)do -j=j+1 -if j>10 then -break -end -local pos=COORDINATE:New(wp.x,wp.alt,wp.y) -local name=string.format("Waypoint %d",i-1) -if wp.name and wp.name~=""then -name=string.format("Waypoint %s",wp.name) -end -local submenu=missionCommands.addSubMenuForGroup(GID,name,self.group[GID].player[UID].menu_waypoints) -missionCommands.addCommandForGroup(GID,"Weather Report",submenu,self.ReportWeather,self,GID,UID,pos,name) -missionCommands.addCommandForGroup(GID,"Request BR",submenu,self.ReportBR,self,GID,UID,pos,name) -end -end -self.group[GID].player[UID].menu_reportalt=missionCommands.addCommandForGroup(GID,"Talk me down",self.group[GID].player[UID].menu_own,self.AltidudeTimerToggle,self,GID,UID) -self.group[GID].player[UID].menu_requestalt=missionCommands.addCommandForGroup(GID,"Request altitude",self.group[GID].player[UID].menu_own,self.ReportHeight,self,GID,UID) -end -function PSEUDOATC:ReportWeather(GID,UID,position,location) -self:F({GID=GID,UID=UID,position=position,location=location}) -local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername)or _SETTINGS -local text=string.format("Local weather at %s:\n",location) -local Pqnh=position:GetPressure(0) -local Pqfe=position:GetPressure() -local hPa2inHg=0.0295299830714 -local hPa2mmHg=0.7500615613030 -local _Pqnh=string.format("%.2f inHg",Pqnh*hPa2inHg) -local _Pqfe=string.format("%.2f inHg",Pqfe*hPa2inHg) -if settings:IsMetric()then -_Pqnh=string.format("%.1f mmHg",Pqnh*hPa2mmHg) -_Pqfe=string.format("%.1f mmHg",Pqfe*hPa2mmHg) -end -text=text..string.format("QFE %.1f hPa = %s.\n",Pqfe,_Pqfe) -text=text..string.format("QNH %.1f hPa = %s.\n",Pqnh,_Pqnh) -local T=position:GetTemperature() -local _T=string.format('%d°F',UTILS.CelsiusToFahrenheit(T)) -if settings:IsMetric()then -_T=string.format('%d°C',T) -end -local text=text..string.format("Temperature %s\n",_T) -local Dir,Vel=position:GetWind() -local Bn,Bd=UTILS.BeaufortScale(Vel) -local Ds=string.format('%03d°',Dir) -local Vs=string.format("%.1f knots",UTILS.MpsToKnots(Vel)) -if settings:IsMetric()then -Vs=string.format('%.1f m/s',Vel) -end -local text=text..string.format("%s, Wind from %s at %s (%s).",self.group[GID].player[UID].playername,Ds,Vs,Bd) -self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,text,self.mdur,true) -end -function PSEUDOATC:ReportBR(GID,UID,position,location) -self:F({GID=GID,UID=UID,position=position,location=location}) -local unit=self.group[GID].player[UID].unit -local coord=unit:GetCoordinate() -local angle=coord:HeadingTo(position) -local range=coord:Get2DDistance(position) -local Bs=string.format('%03d°',angle) -local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername)or _SETTINGS -local Rs=string.format("%.1f NM",UTILS.MetersToNM(range)) -if settings:IsMetric()then -Rs=string.format("%.1f km",range/1000) -end -local text=string.format("%s: Bearing %s, Range %s.",location,Bs,Rs) -self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,text,self.mdur,true) -end -function PSEUDOATC:ReportHeight(GID,UID,dt,_clear) -self:F({GID=GID,UID=UID,dt=dt}) -local dt=dt or self.mdur -if _clear==nil then -_clear=false -end -local function get_AGL(p) -local agl=0 -local vec2={x=p.x,y=p.z} -local ground=land.getHeight(vec2) -local agl=p.y-ground -return agl -end -local unit=self.group[GID].player[UID].unit -if unit and unit:IsAlive()then -local position=unit:GetCoordinate() -local height=get_AGL(position) -local callsign=unit:GetCallsign() -local PlayerName=self.group[GID].player[UID].playername -local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername)or _SETTINGS -local Hs=string.format("%d ft",UTILS.MetersToFeet(height)) -if settings:IsMetric()then -Hs=string.format("%d m",height) -end -local _text=string.format("%s, your altitude is %s AGL.",callsign,Hs) -if self.reportplayername then -_text=string.format("%s, your altitude is %s AGL.",PlayerName,Hs) -end -if _clear==false then -_text=_text..string.format(" FL%03d.",position.y/30.48) -end -self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,_text,dt,_clear) -return height -end -return 0 -end -function PSEUDOATC:AltidudeTimerToggle(GID,UID) -self:F({GID=GID,UID=UID}) -if self.group[GID].player[UID].altimerid then -self:AltitudeTimerStop(GID,UID) -else -self:AltitudeTimeStart(GID,UID) -end -end -function PSEUDOATC:AltitudeTimeStart(GID,UID) -self:F({GID=GID,UID=UID}) -self:T(PSEUDOATC.id..string.format("Starting altitude report timer for player ID %d.",UID)) -self.group[GID].player[UID].altimer,self.group[GID].player[UID].altimerid=SCHEDULER:New(nil,self.ReportHeight,{self,GID,UID,1,true},1,3) -end -function PSEUDOATC:AltitudeTimerStop(GID,UID) -self:F({GID=GID,UID=UID}) -self:T(PSEUDOATC.id..string.format("Stopping altitude report timer for player ID %d.",UID)) -if self.group[GID].player[UID].altimerid then -self.group[GID].player[UID].altimer:Stop(self.group[GID].player[UID].altimerid) -end -self.group[GID].player[UID].altimer=nil -self.group[GID].player[UID].altimerid=nil -end -function PSEUDOATC:LocalAirports(GID,UID) -self:F({GID=GID,UID=UID}) -self.group[GID].player[UID].airports=nil -self.group[GID].player[UID].airports={} -local pos=self.group[GID].player[UID].unit:GetCoordinate() -for i=0,2 do -local airports=coalition.getAirbases(i) -for _,airbase in pairs(airports)do -local name=airbase:getName() -local a=AIRBASE:FindByName(name) -if a then -local q=a:GetCoordinate() -local d=q:Get2DDistance(pos) -table.insert(self.group[GID].player[UID].airports,{distance=d,name=name}) -end -end -end -local function compare(a,b) -return a.distance0 then -local samecoalition=anycoalition or Coalition==warehouse:GetCoalition() -if samecoalition and not(warehouse:IsNotReadyYet()or warehouse:IsStopped()or warehouse:IsDestroyed())then -local nassets=warehouse:GetNumberOfAssets(Descriptor,DescriptorValue) -local enough=true -if Descriptor and DescriptorValue then -enough=nassets>=MinAssets -end -if enough and(distmin==nil or dist=1 then -local FSMstate=self:GetState() -local coalition=self:GetCoalitionName() -local country=self:GetCountryName() -self:I(self.lid..string.format("State=%s %s [%s]: Assets=%d, Requests: waiting=%d, pending=%d",FSMstate,country,coalition,#self.stock,#self.queue,#self.pending)) -end -self:_JobDone() -self:_DisplayStatus() -self:_CheckConquered() -if self:IsRunwayOperational()==false then -local Trepair=self:GetRunwayRepairtime() -self:I(self.lid..string.format("Runway destroyed! Will be repaired in %d sec",Trepair)) -if Trepair==0 then -self.runwaydestroyed=nil -self:RunwayRepaired() -end -end -self:_CheckRequestConsistancy(self.queue) -if self:IsRunning()or self:IsAttacked()then -local request=self:_CheckQueue() -if request then -self:Request(request) -end -end -if self.verbosity>2 then -self:_PrintQueue(self.queue,"Queue waiting") -self:_PrintQueue(self.pending,"Queue pending") -end -self:_UpdateWarehouseMarkText() -if self.Debug then -self:_DisplayStockItems(self.stock) -end -self:__Status(-self.dTstatus) -end -function WAREHOUSE:_JobDone() -local done={} -for _,request in pairs(self.pending)do -local request=request -if request.born then -local ncargo=0 -if request.cargogroupset then -ncargo=request.cargogroupset:Count() -end -local ntransport=0 -if request.transportgroupset then -ntransport=request.transportgroupset:Count() -end -local ncargotot=request.nasset -local ncargodelivered=request.ndelivered -local ncargodead=ncargotot-ncargodelivered-ncargo -local ntransporttot=request.ntransport -local ntransporthome=request.ntransporthome -local ntransportdead=ntransporttot-ntransporthome-ntransport -local text=string.format("Request id=%d: Cargo: Ntot=%d, Nalive=%d, Ndelivered=%d, Ndead=%d | Transport: Ntot=%d, Nalive=%d, Nhome=%d, Ndead=%d", -request.uid,ncargotot,ncargo,ncargodelivered,ncargodead,ntransporttot,ntransport,ntransporthome,ntransportdead) -self:T(self.lid..text) -if ncargo==0 then -if not self.delivered[request.uid]then -self:Delivered(request) -end -if ntransport==0 then -if self.verbosity>=1 then -local text=string.format("Warehouse %s: Job on request id=%d for warehouse %s done!\n",self.alias,request.uid,request.warehouse.alias) -text=text..string.format("- %d of %d assets delivered. Casualties %d.",ncargodelivered,ncargotot,ncargodead) -if request.ntransport>0 then -text=text..string.format("\n- %d of %d transports returned home. Casualties %d.",ntransporthome,ntransporttot,ntransportdead) -end -self:_InfoMessage(text,20) -end -table.insert(done,request) -else -for _,_group in pairs(request.transportgroupset:GetSetObjects())do -local group=_group -if group and group:IsAlive()then -local category=group:GetCategory() -local speed=group:GetVelocityKMH() -local notmoving=speed<1 -local airbase=group:GetCoordinate():GetClosestAirbase():GetName() -local athomebase=self.airbase and self.airbase:GetName()==airbase -local onground=not group:InAir() -local inspawnzone=group:IsPartlyOrCompletelyInZone(self.spawnzone) -local ishome=false -if category==Group.Category.GROUND or category==Group.Category.HELICOPTER then -ishome=inspawnzone and onground and notmoving -elseif category==Group.Category.AIRPLANE then -ishome=athomebase and onground and notmoving -end -local text=string.format("Group %s: speed=%d km/h, onground=%s , airbase=%s, spawnzone=%s ==> ishome=%s",group:GetName(),speed,tostring(onground),airbase,tostring(inspawnzone),tostring(ishome)) -self:T(self.lid..text) -if ishome then -local text=string.format("Warehouse %s: Transport group arrived back home and no cargo left for request id=%d.\nSending transport group %s back to stock.",self.alias,request.uid,group:GetName()) -self:T(self.lid..text) -if self.Debug then -group:SmokeRed() -end -self:Arrived(group) -end -end -end -end -else -if ntransport==0 and request.ntransport>0 then -local ncargoalive=0 -for _,_group in pairs(request.cargogroupset:GetSetObjects())do -local groupname=_group:GetName() -local group=GROUP:FindByName(groupname.."#CARGO") -if group and group:IsAlive()then -if group:IsPartlyOrCompletelyInZone(self.spawnzone)then -if self.Debug then -group:SmokeBlue() -end -self:AddAsset(group) -ncargoalive=ncargoalive+1 -end -end -end -self:_InfoMessage(string.format("Warehouse %s: All transports of request id=%s dead! Putting remaining %s cargo assets back into warehouse!",self.alias,request.uid,ncargoalive)) -end -end -end -end -for _,request in pairs(done)do -self:_DeleteQueueItem(request,self.pending) -end -end -function WAREHOUSE:_CheckAssetStatus() -local function _CheckGroup(_request,_group) -local request=_request -local group=_group -if group and group:IsAlive()then -local category=group:GetCategory() -for _,_unit in pairs(group:GetUnits())do -local unit=_unit -if unit and unit:IsAlive()then -local unitid=unit:GetID() -local life9=unit:GetLife() -local life0=unit:GetLife0() -local life=life9/life0*100 -local speed=unit:GetVelocityMPS() -local onground=unit:InAir() -local problem=false -if life<10 then -self:T(string.format("Unit %s is heavily damaged!",unit:GetName())) -end -if speed<1 and unit:GetSpeedMax()>1 and onground then -self:T(string.format("Unit %s is not moving!",unit:GetName())) -problem=true -end -if problem then -if request.assetproblem[unitid]then -local deltaT=timer.getAbsTime()-request.assetproblem[unitid] -if deltaT>300 then -unit:Destroy() -end -else -request.assetproblem[unitid]=timer.getAbsTime() -end -end -end -end -end -end -for _,request in pairs(self.pending)do -local request=request -if request.cargogroupset then -for _,_group in pairs(request.cargogroupset:GetSet())do -local group=_group -_CheckGroup(request,group) -end -end -if request.transportgroupset then -for _,group in pairs(request.transportgroupset:GetSet())do -_CheckGroup(request,group) -end -end -end -end -function WAREHOUSE:onafterAddAsset(From,Event,To,group,ngroups,forceattribute,forcecargobay,forceweight,loadradius,skill,liveries,assignment,other) -self:T({group=group,ngroups=ngroups,forceattribute=forceattribute,forcecargobay=forcecargobay,forceweight=forceweight}) -local n=ngroups or 1 -if type(group)=="string"then -group=GROUP:FindByName(group) -end -if liveries and type(liveries)=="string"then -liveries={liveries} -end -if group then -local wid,aid,rid=self:_GetIDsFromGroup(group) -if wid and aid and rid then -local warehouse=self:FindWarehouseInDB(wid) -if warehouse then -local request=warehouse:_GetRequestOfGroup(group,warehouse.pending) -if request then -local istransport=warehouse:_GroupIsTransport(group,request) -if istransport==true then -request.ntransporthome=request.ntransporthome+1 -request.transportgroupset:Remove(group:GetName(),true) -local ntrans=request.transportgroupset:Count() -self:T2(warehouse.lid..string.format("Transport %d of %s returned home. TransportSet=%d",request.ntransporthome,tostring(request.ntransport),ntrans)) -elseif istransport==false then -request.ndelivered=request.ndelivered+1 -local namewo=self:_GetNameWithOut(group) -request.cargogroupset:Remove(namewo,true) -local ncargo=request.cargogroupset:Count() -self:T2(warehouse.lid..string.format("Cargo %s: %d of %s delivered. CargoSet=%d",namewo,request.ndelivered,tostring(request.nasset),ncargo)) -else -self:E(warehouse.lid..string.format("WARNING: Group %s is neither cargo nor transport! Need to investigate...",group:GetName())) -end -if assignment==nil and request.assignment~=nil then -assignment=request.assignment -end -end -end -local asset=self:FindAssetInDB(group) -if asset~=nil then -self:_DebugMessage(string.format("Warehouse %s: Adding KNOWN asset uid=%d with attribute=%s to stock.",self.alias,asset.uid,asset.attribute),5) -if liveries then -if type(liveries)=="table"then -asset.livery=liveries[math.random(#liveries)] -else -asset.livery=liveries -end -end -asset.skill=skill or asset.skill -asset.wid=self.uid -asset.rid=nil -asset.spawned=false -asset.requested=false -asset.isReserved=false -asset.iscargo=nil -asset.arrived=nil -if group:IsAlive()==true then -asset.damage=asset.life0-group:GetLife() -end -table.insert(self.stock,asset) -self:__NewAsset(0.1,asset,assignment or"") -else -self:_ErrorMessage(string.format("ERROR: Known asset could not be found in global warehouse db!"),0) -end -else -self:_DebugMessage(string.format("Warehouse %s: Adding %d NEW assets of group %s to stock",self.alias,n,tostring(group:GetName())),5) -local assets=self:_RegisterAsset(group,n,forceattribute,forcecargobay,forceweight,loadradius,liveries,skill,assignment) -for _,asset in pairs(assets)do -asset.wid=self.uid -asset.rid=nil -table.insert(self.stock,asset) -self:__NewAsset(0.1,asset,assignment or"") -end -end -if group:IsAlive()==true then -self:_DebugMessage(string.format("Removing group %s",group:GetName()),5) -local opsgroup=_DATABASE:GetOpsGroup(group:GetName()) -if opsgroup then -opsgroup:Despawn(0,true) -opsgroup:__Stop(-0.01) -else -group:Destroy() -end -else -local opsgroup=_DATABASE:GetOpsGroup(group:GetName()) -if opsgroup then -opsgroup:Stop() -end -end -else -self:E(self.lid.."ERROR: Unknown group added as asset!") -self:E({unknowngroup=group}) -end -end -function WAREHOUSE:_RegisterAsset(group,ngroups,forceattribute,forcecargobay,forceweight,loadradius,liveries,skill,assignment) -self:F({groupname=group:GetName(),ngroups=ngroups,forceattribute=forceattribute,forcecargobay=forcecargobay,forceweight=forceweight}) -local n=ngroups or 1 -local function _GetObjectSize(DCSdesc) -if DCSdesc.box then -local x=DCSdesc.box.max.x-DCSdesc.box.min.x -local y=DCSdesc.box.max.y-DCSdesc.box.min.y -local z=DCSdesc.box.max.z-DCSdesc.box.min.z -return math.max(x,z),x,y,z -end -return 0,0,0,0 -end -local templategroupname=group:GetName() -local Descriptors=group:GetUnit(1):GetDesc() -local Category=group:GetCategory() -local TypeName=group:GetTypeName() -local SpeedMax=group:GetSpeedMax() -local RangeMin=group:GetRange() -local smax,sx,sy,sz=_GetObjectSize(Descriptors) -local weight=0 -local cargobay={} -local cargobaytot=0 -local cargobaymax=0 -local weights={} -for _i,_unit in pairs(group:GetUnits())do -local unit=_unit -local Desc=unit:GetDesc() -local unitweight=forceweight or Desc.massEmpty -if unitweight then -weight=weight+unitweight -weights[_i]=unitweight -end -local cargomax=0 -local massfuel=Desc.fuelMassMax or 0 -local massempty=Desc.massEmpty or 0 -local massmax=Desc.massMax or 0 -cargomax=massmax-massfuel-massempty -self:T3(self.lid..string.format("Unit name=%s: mass empty=%.1f kg, fuel=%.1f kg, max=%.1f kg ==> cargo=%.1f kg",unit:GetName(),unitweight,massfuel,massmax,cargomax)) -local bay=forcecargobay or unit:GetCargoBayFreeWeight() -table.insert(cargobay,bay) -cargobaytot=cargobaytot+bay -if bay>cargobaymax then -cargobaymax=bay -end -end -local attribute=forceattribute or self:_GetAttribute(group) -local assets={} -for i=1,n do -local asset={} -_WAREHOUSEDB.AssetID=_WAREHOUSEDB.AssetID+1 -asset.uid=_WAREHOUSEDB.AssetID -asset.templatename=templategroupname -asset.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) -asset.category=Category -asset.unittype=TypeName -asset.nunits=#asset.template.units -asset.range=RangeMin -asset.speedmax=SpeedMax -asset.size=smax -asset.weight=weight -asset.weights=weights -asset.DCSdesc=Descriptors -asset.attribute=attribute -asset.cargobay=cargobay -asset.cargobaytot=cargobaytot -asset.cargobaymax=cargobaymax -asset.loadradius=loadradius -if liveries then -asset.livery=liveries[math.random(#liveries)] -end -asset.skill=skill -asset.assignment=assignment -asset.spawned=false -asset.requested=false -asset.isReserved=false -asset.life0=group:GetLife0() -asset.damage=0 -asset.spawngroupname=string.format("%s_AID-%d",templategroupname,asset.uid) -if i==1 then -self:_AssetItemInfo(asset) -end -_WAREHOUSEDB.Assets[asset.uid]=asset -table.insert(assets,asset) -end -return assets -end -function WAREHOUSE:_AssetItemInfo(asset) -local text=string.format("\nNew asset with id=%d for warehouse %s:\n",asset.uid,self.alias) -text=text..string.format("Spawngroup name= %s\n",asset.spawngroupname) -text=text..string.format("Template name = %s\n",asset.templatename) -text=text..string.format("Unit type = %s\n",asset.unittype) -text=text..string.format("Attribute = %s\n",asset.attribute) -text=text..string.format("Category = %d\n",asset.category) -text=text..string.format("Units # = %d\n",asset.nunits) -text=text..string.format("Speed max = %5.2f km/h\n",asset.speedmax) -text=text..string.format("Range max = %5.2f km\n",asset.range/1000) -text=text..string.format("Size max = %5.2f m\n",asset.size) -text=text..string.format("Weight total = %5.2f kg\n",asset.weight) -text=text..string.format("Cargo bay tot = %5.2f kg\n",asset.cargobaytot) -text=text..string.format("Cargo bay max = %5.2f kg\n",asset.cargobaymax) -text=text..string.format("Load radius = %s m\n",tostring(asset.loadradius)) -text=text..string.format("Skill = %s\n",tostring(asset.skill)) -text=text..string.format("Livery = %s",tostring(asset.livery)) -self:I(self.lid..text) -self:T({DCSdesc=asset.DCSdesc}) -self:T3({Template=asset.template}) -end -function WAREHOUSE:onafterNewAsset(From,Event,To,asset,assignment) -self:T(self.lid..string.format("New asset %s id=%d with assignment %s.",tostring(asset.templatename),asset.uid,tostring(assignment))) -end -function WAREHOUSE:onbeforeAddRequest(From,Event,To,warehouse,AssetDescriptor,AssetDescriptorValue,nAsset,TransportType,nTransport,Assignment,Prio) -local okay=true -if AssetDescriptor==WAREHOUSE.Descriptor.ATTRIBUTE then -local gotit=false -for _,attribute in pairs(WAREHOUSE.Attribute)do -if AssetDescriptorValue==attribute then -gotit=true -end -end -if not gotit then -self:_ErrorMessage("ERROR: Invalid request. Asset attribute is unknown!",5) -okay=false -end -elseif AssetDescriptor==WAREHOUSE.Descriptor.CATEGORY then -local gotit=false -for _,category in pairs(Group.Category)do -if AssetDescriptorValue==category then -gotit=true -end -end -if not gotit then -self:_ErrorMessage("ERROR: Invalid request. Asset category is unknown!",5) -okay=false -end -elseif AssetDescriptor==WAREHOUSE.Descriptor.GROUPNAME then -if type(AssetDescriptorValue)~="string"then -self:_ErrorMessage("ERROR: Invalid request. Asset template name must be passed as a string!",5) -okay=false -end -elseif AssetDescriptor==WAREHOUSE.Descriptor.UNITTYPE then -if type(AssetDescriptorValue)~="string"then -self:_ErrorMessage("ERROR: Invalid request. Asset unit type must be passed as a string!",5) -okay=false -end -elseif AssetDescriptor==WAREHOUSE.Descriptor.ASSIGNMENT then -if type(AssetDescriptorValue)~="string"then -self:_ErrorMessage("ERROR: Invalid request. Asset assignment type must be passed as a string!",5) -okay=false -end -elseif AssetDescriptor==WAREHOUSE.Descriptor.ASSETLIST then -if type(AssetDescriptorValue)~="table"then -self:_ErrorMessage("ERROR: Invalid request. Asset assignment type must be passed as a table!",5) -okay=false -end -else -self:_ErrorMessage("ERROR: Invalid request. Asset descriptor is not ATTRIBUTE, CATEGORY, GROUPNAME, UNITTYPE or ASSIGNMENT!",5) -okay=false -end -if self:IsStopped()then -self:_ErrorMessage("ERROR: Invalid request. Warehouse is stopped!",0) -okay=false -end -if self:IsDestroyed()and not self.respawnafterdestroyed then -self:_ErrorMessage("ERROR: Invalid request. Warehouse is destroyed!",0) -okay=false -end -return okay -end -function WAREHOUSE:onafterAddRequest(From,Event,To,warehouse,AssetDescriptor,AssetDescriptorValue,nAsset,TransportType,nTransport,Prio,Assignment) -nAsset=nAsset or 1 -TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED -Prio=Prio or 50 -if nTransport==nil then -if TransportType==WAREHOUSE.TransportType.SELFPROPELLED then -nTransport=0 -else -nTransport=1 -end -end -local toself=false -if self.warehouse:GetName()==warehouse.warehouse:GetName()then -toself=true -end -self.queueid=self.queueid+1 -local request={ -uid=self.queueid, -prio=Prio, -warehouse=warehouse, -assetdesc=AssetDescriptor, -assetdescval=AssetDescriptorValue, -nasset=nAsset, -transporttype=TransportType, -ntransport=nTransport, -assignment=tostring(Assignment), -airbase=warehouse:GetAirbase(), -category=warehouse:GetAirbaseCategory(), -ndelivered=0, -ntransporthome=0, -assets={}, -toself=toself, -} -table.insert(self.queue,request) -local descval="assetlist" -if request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST then -else -descval=tostring(request.assetdescval) -end -local text=string.format("Warehouse %s: New request from warehouse %s.\nDescriptor %s=%s, #assets=%s; Transport=%s, #transports=%s.", -self.alias,warehouse.alias,request.assetdesc,descval,tostring(request.nasset),request.transporttype,tostring(request.ntransport)) -self:_DebugMessage(text,5) -end -function WAREHOUSE:onbeforeRequest(From,Event,To,Request) -self:T3({warehouse=self.alias,request=Request}) -local distance=self:GetCoordinate():Get2DDistance(Request.warehouse:GetCoordinate()) -local _assets=Request.cargoassets -if Request.nasset==0 then -local text=string.format("Warehouse %s: Request denied! Zero assets were requested.",self.alias) -self:_InfoMessage(text,10) -return false -end -for _,_asset in pairs(_assets)do -local asset=_asset -if asset.range=1 then -local text=string.format("Warehouse %s: Processing request id=%d from warehouse %s.\n",self.alias,Request.uid,Request.warehouse.alias) -text=text..string.format("Requested %s assets of %s=%s.\n",tostring(Request.nasset),Request.assetdesc,Request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST and"Asset list"or Request.assetdescval) -text=text..string.format("Transports %s of type %s.",tostring(Request.ntransport),tostring(Request.transporttype)) -self:_InfoMessage(text,5) -end -Request.timestamp=timer.getAbsTime() -self:_SpawnAssetRequest(Request) -local _assetstock=Request.transportassets -local Parking={} -if Request.transportcategory==Group.Category.AIRPLANE or Request.transportcategory==Group.Category.HELICOPTER then -Parking=self:_FindParkingForAssets(self.airbase,_assetstock) -end -local _transportassets={} -for i=1,Request.ntransport do -local _assetitem=_assetstock[i] -local _alias=_assetitem.spawngroupname -_assetitem.rid=Request.uid -_assetitem.spawned=false -_assetitem.iscargo=false -_assetitem.arrived=false -local spawngroup=nil -Request.assets[_assetitem.uid]=_assetitem -if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then -spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem,Request,Parking[_assetitem.uid],true) -elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then -spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem,Request,Parking[_assetitem.uid],false) -elseif Request.transporttype==WAREHOUSE.TransportType.APC then -spawngroup=self:_SpawnAssetGroundNaval(_alias,_assetitem,Request,self.spawnzone) -elseif Request.transporttype==WAREHOUSE.TransportType.TRAIN then -self:_ErrorMessage("ERROR: Cargo transport by train not supported yet!") -return -elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.NAVALCARRIER -or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then -spawngroup=self:_SpawnAssetGroundNaval(_alias,_assetitem,Request,self.portzone) -elseif Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then -self:_ErrorMessage("ERROR: Transport type selfpropelled was already handled above. We should not get here!") -return -else -self:_ErrorMessage("ERROR: Unknown transport type!") -return -end -if spawngroup then -self:__AssetSpawned(0.01,spawngroup,_assetitem,Request) -end -end -Request.assetproblem={} -table.insert(self.pending,Request) -self:_DeleteQueueItem(Request,self.queue) -end -function WAREHOUSE:onafterRequestSpawned(From,Event,To,Request,CargoGroupSet,TransportGroupSet) -local _cargotype=Request.cargoattribute -local _cargocategory=Request.cargocategory -if Request.toself then -self:_DebugMessage(string.format("Selfrequest! Current status %s",self:GetState())) -self:__SelfRequest(1,CargoGroupSet,Request) -return -end -if Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then -self:T2(self.lid..string.format("Got selfpropelled request for %d assets.",CargoGroupSet:Count())) -for _,_group in pairs(CargoGroupSet:GetSetObjects())do -local group=_group -if _cargocategory==Group.Category.GROUND then -self:T2(self.lid..string.format("Route ground group %s.",group:GetName())) -local ToCoordinate=Request.warehouse.spawnzone:GetRandomCoordinate() -if self.Debug then -ToCoordinate:MarkToAll(string.format("Destination of group %s",group:GetName())) -end -self:_RouteGround(group,Request) -elseif _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then -self:T2(self.lid..string.format("Route airborne group %s.",group:GetName())) -self:_RouteAir(group) -elseif _cargocategory==Group.Category.SHIP then -self:T2(self.lid..string.format("Route naval group %s.",group:GetName())) -self:_RouteNaval(group,Request) -elseif _cargocategory==Group.Category.TRAIN then -self:T2(self.lid..string.format("Route train group %s.",group:GetName())) -self:_RouteTrain(group,Request.warehouse.rail) -else -self:E(self.lid..string.format("ERROR: unknown category %s for self propelled cargo %s!",tostring(_cargocategory),tostring(group:GetName()))) -end -end -Request.transportgroupset=TransportGroupSet -return -end -local _boardradius=500 -if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then -_boardradius=5000 -elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then -elseif Request.transporttype==WAREHOUSE.TransportType.APC then -elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER -or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then -_boardradius=6000 -end -local CargoGroups=SET_CARGO:New() -for _,_group in pairs(CargoGroupSet:GetSetObjects())do -local asset=self:FindAssetInDB(_group) -local cargogroup=CARGO_GROUP:New(_group,_cargotype,_group:GetName(),_boardradius,asset.loadradius) -cargogroup:SetWeight(asset.weight) -CargoGroups:AddCargo(cargogroup) -end -local CargoTransport -if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then -local PickupAirbaseSet=SET_ZONE:New():AddZone(ZONE_AIRBASE:New(self.airbase:GetName())) -local DeployAirbaseSet=SET_ZONE:New():AddZone(ZONE_AIRBASE:New(Request.airbase:GetName())) -CargoTransport=AI_CARGO_DISPATCHER_AIRPLANE:New(TransportGroupSet,CargoGroups,PickupAirbaseSet,DeployAirbaseSet) -CargoTransport:SetHomeZone(ZONE_AIRBASE:New(self.airbase:GetName())) -elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then -local PickupZoneSet=SET_ZONE:New():AddZone(self.spawnzone) -local DeployZoneSet=SET_ZONE:New():AddZone(Request.warehouse.spawnzone) -CargoTransport=AI_CARGO_DISPATCHER_HELICOPTER:New(TransportGroupSet,CargoGroups,PickupZoneSet,DeployZoneSet) -CargoTransport:SetHomeZone(self.spawnzone) -elseif Request.transporttype==WAREHOUSE.TransportType.APC then -local PickupZoneSet=SET_ZONE:New():AddZone(self.spawnzone) -local DeployZoneSet=SET_ZONE:New():AddZone(Request.warehouse.spawnzone) -CargoTransport=AI_CARGO_DISPATCHER_APC:New(TransportGroupSet,CargoGroups,PickupZoneSet,DeployZoneSet,0) -CargoTransport:SetHomeZone(self.spawnzone) -elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER -or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then -local PickupZoneSet=SET_ZONE:New():AddZone(self.portzone) -PickupZoneSet:AddZone(self.harborzone) -local DeployZoneSet=SET_ZONE:New():AddZone(Request.warehouse.harborzone) -local remotename=Request.warehouse.warehouse:GetName() -local ShippingLane=self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])] -CargoTransport=AI_CARGO_DISPATCHER_SHIP:New(TransportGroupSet,CargoGroups,PickupZoneSet,DeployZoneSet,ShippingLane) -CargoTransport:SetHomeZone(self.portzone) -else -self:E(self.lid.."ERROR: Unknown transporttype!") -end -local pickupouter=200 -local pickupinner=0 -local deployouter=200 -local deployinner=0 -if Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER -or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then -pickupouter=1000 -pickupinner=20 -deployouter=1000 -deployinner=0 -else -pickupouter=200 -pickupinner=0 -if self.spawnzone.Radius~=nil then -pickupouter=self.spawnzone.Radius -pickupinner=20 -end -deployouter=200 -deployinner=0 -if self.spawnzone.Radius~=nil then -deployouter=Request.warehouse.spawnzone.Radius -deployinner=20 -end -end -CargoTransport:SetPickupRadius(pickupouter,pickupinner) -CargoTransport:SetDeployRadius(deployouter,deployinner) -Request.carriercargo={} -for _,carriergroup in pairs(TransportGroupSet:GetSetObjects())do -local asset=self:FindAssetInDB(carriergroup) -for _i,_carrierunit in pairs(carriergroup:GetUnits())do -local carrierunit=_carrierunit -Request.carriercargo[carrierunit:GetName()]={} -local cargobay=asset.cargobay[_i] -carrierunit:SetCargoBayWeightLimit(cargobay) -self:T2(self.lid..string.format("Cargo bay weight limit of carrier unit %s: %.1f kg.",carrierunit:GetName(),carrierunit:GetCargoBayFreeWeight())) -end -end -function CargoTransport:OnAfterPickedUp(From,Event,To,Carrier,PickupZone) -local warehouse=Carrier:GetState(Carrier,"WAREHOUSE") -local text=string.format("Carrier group %s picked up at pickup zone %s.",Carrier:GetName(),PickupZone:GetName()) -warehouse:T(warehouse.lid..text) -end -function CargoTransport:OnAfterDeployed(From,Event,To,Carrier,DeployZone) -local warehouse=Carrier:GetState(Carrier,"WAREHOUSE") -end -function CargoTransport:OnAfterHome(From,Event,To,Carrier,Coordinate,Speed,Height,HomeZone) -local warehouse=Carrier:GetState(Carrier,"WAREHOUSE") -local text=string.format("Carrier group %s going home to zone %s.",Carrier:GetName(),HomeZone:GetName()) -warehouse:T(warehouse.lid..text) -end -function CargoTransport:OnAfterLoaded(From,Event,To,Carrier,Cargo,CarrierUnit,PickupZone) -local warehouse=Carrier:GetState(Carrier,"WAREHOUSE") -local text=string.format("Carrier group %s loaded cargo %s into unit %s in pickup zone %s",Carrier:GetName(),Cargo:GetName(),CarrierUnit:GetName(),PickupZone:GetName()) -warehouse:T(warehouse.lid..text) -local group=Cargo:GetObject() -local request=warehouse:_GetRequestOfGroup(group,warehouse.pending) -table.insert(request.carriercargo[CarrierUnit:GetName()],warehouse:_GetNameWithOut(Cargo:GetName())) -end -function CargoTransport:OnAfterUnloaded(From,Event,To,Carrier,Cargo,CarrierUnit,DeployZone) -local warehouse=Carrier:GetState(Carrier,"WAREHOUSE") -local group=Cargo:GetObject() -local text=string.format("Cargo group %s was unloaded from carrier unit %s.",tostring(group:GetName()),tostring(CarrierUnit:GetName())) -warehouse:T(warehouse.lid..text) -warehouse:Arrived(group) -end -function CargoTransport:OnAfterBackHome(From,Event,To,Carrier) -local carrier=Carrier -local warehouse=carrier:GetState(carrier,"WAREHOUSE") -carrier:SmokeWhite() -local text=string.format("Carrier %s is back home at warehouse %s.",tostring(Carrier:GetName()),tostring(warehouse.warehouse:GetName())) -MESSAGE:New(text,5):ToAllIf(warehouse.Debug) -warehouse:I(warehouse.lid..text) -warehouse:__Arrived(1,Carrier) -end -CargoTransport:__Start(5) -end -function WAREHOUSE:onafterUnloaded(From,Event,To,group) -self:_DebugMessage(string.format("Cargo %s unloaded!",tostring(group:GetName())),5) -if group and group:IsAlive()then -if self.Debug then -group:SmokeWhite() -end -local speedmax=group:GetSpeedMax() -if group:IsGround()then -if speedmax>1 then -group:RouteGroundTo(self.spawnzone:GetRandomCoordinate(),speedmax*0.5,AI.Task.VehicleFormation.RANK,3) -else -self:Arrived(group) -end -elseif group:IsAir()then -self:Arrived(group) -elseif group:IsShip()then -self:Arrived(group) -end -else -self:E(self.lid..string.format("ERROR unloaded Cargo group is not alive!")) -end -end -function WAREHOUSE:onbeforeArrived(From,Event,To,group) -local asset=self:FindAssetInDB(group) -if asset then -if asset.flightgroup and not asset.arrived then -asset.arrived=true -return false -end -if asset.arrived==true then -return false -else -asset.arrived=true -return true -end -end -end -function WAREHOUSE:onafterArrived(From,Event,To,group) -if self.Debug then -group:SmokeOrange() -end -local request=self:_GetRequestOfGroup(group,self.pending) -if request then -local warehouse=request.warehouse -local istransport=self:_GroupIsTransport(group,request) -if istransport==true then -warehouse=self -elseif istransport==false then -warehouse=request.warehouse -else -self:E(self.lid..string.format("ERROR: Group %s is neither cargo nor transport",group:GetName())) -return -end -self:_DebugMessage(string.format("Group %s arrived at warehouse %s!",tostring(group:GetName()),warehouse.alias),5) -if group:IsGround()and group:GetSpeedMax()>1 then -group:RouteGroundTo(warehouse:GetCoordinate(),group:GetSpeedMax()*0.3,"Off Road") -end -self:T(self.lid.."Asset arrived at warehouse adding in 60 sec") -warehouse:__AddAsset(60,group) -end -end -function WAREHOUSE:onafterDelivered(From,Event,To,request) -if self.verbosity>=1 then -local text=string.format("Warehouse %s: All assets delivered to warehouse %s!",self.alias,request.warehouse.alias) -self:_InfoMessage(text,5) -end -if self.Debug then -self:_Fireworks(request.warehouse:GetCoordinate()) -end -self.delivered[request.uid]=true -end -function WAREHOUSE:onafterSelfRequest(From,Event,To,groupset,request) -self:_DebugMessage(string.format("Assets spawned at warehouse %s after self request!",self.alias)) -for _,_group in pairs(groupset:GetSetObjects())do -local group=_group -if self.Debug then -group:FlareGreen() -end -end -if self:IsAttacked()then -if self.autodefence then -for _,_group in pairs(groupset:GetSetObjects())do -local group=_group -local speedmax=group:GetSpeedMax() -if group:IsGround()and speedmax>1 and group:IsNotInZone(self.zone)then -group:RouteGroundTo(self.zone:GetRandomCoordinate(),0.8*speedmax,"Off Road") -end -end -end -table.insert(self.defending,request) -end -end -function WAREHOUSE:onafterAttacked(From,Event,To,Coalition,Country) -local text=string.format("Warehouse %s: We are under attack!",self.alias) -self:_InfoMessage(text) -if self.Debug then -self:GetCoordinate():SmokeOrange() -end -if self.autodefence then -local nground=self:GetNumberOfAssets(WAREHOUSE.Descriptor.CATEGORY,Group.Category.GROUND) -local text=string.format("Warehouse auto defence activated.\n") -if nground>0 then -text=text..string.format("Deploying all %d ground assets.",nground) -self:AddRequest(self,WAREHOUSE.Descriptor.CATEGORY,Group.Category.GROUND,WAREHOUSE.Quantity.ALL,nil,nil,0,"AutoDefence") -else -text=text..string.format("No ground assets currently available.") -end -self:_InfoMessage(text) -else -local text=string.format("Warehouse auto defence inactive.") -self:I(self.lid..text) -end -end -function WAREHOUSE:onafterDefeated(From,Event,To) -local text=string.format("Warehouse %s: Enemy attack was defeated!",self.alias) -self:_InfoMessage(text) -if self.Debug then -self:GetCoordinate():SmokeGreen() -end -if self.autodefence then -for _,request in pairs(self.defending)do -for _,_group in pairs(request.cargogroupset:GetSetObjects())do -local group=_group -local speed=group:GetSpeedMax() -if group:IsGround()and speed>1 then -group:RouteGroundTo(self:GetCoordinate(),speed*0.3) -end -self:__AddAsset(60,group) -end -end -self.defending=nil -self.defending={} -end -end -function WAREHOUSE:onafterRespawn(From,Event,To) -local text=string.format("Respawning warehouse %s",self.alias) -self:_InfoMessage(text) -self.warehouse:ReSpawn() -end -function WAREHOUSE:onbeforeChangeCountry(From,Event,To,Country) -local currentCountry=self:GetCountry() -local text=string.format("Warehouse %s: request to change country %d-->%d",self.alias,currentCountry,Country) -self:_DebugMessage(text,10) -if currentCountry~=Country then -return true -end -return false -end -function WAREHOUSE:onafterChangeCountry(From,Event,To,Country) -local CoalitionOld=self:GetCoalition() -self.warehouse:ReSpawn(Country) -local CoalitionNew=self:GetCoalition() -self.queue=nil -self.queue={} -if self.airbasename then -local airbase=AIRBASE:FindByName(self.airbasename) -local airbaseCoalition=airbase:GetCoalition() -if CoalitionNew==airbaseCoalition then -self.airbase=airbase -else -self.airbase=nil -end -end -if self.Debug then -if CoalitionNew==coalition.side.RED then -self:GetCoordinate():SmokeRed() -elseif CoalitionNew==coalition.side.BLUE then -self:GetCoordinate():SmokeBlue() -end -end -end -function WAREHOUSE:onbeforeCaptured(From,Event,To,Coalition,Country) -self:ChangeCountry(Country) -end -function WAREHOUSE:onafterCaptured(From,Event,To,Coalition,Country) -local text=string.format("Warehouse %s: We were captured by enemy coalition (side=%d)!",self.alias,Coalition) -self:_InfoMessage(text) -end -function WAREHOUSE:onafterAirbaseCaptured(From,Event,To,Coalition) -local text=string.format("Warehouse %s: Our airbase %s was captured by the enemy (coalition=%d)!",self.alias,self.airbasename,Coalition) -self:_InfoMessage(text) -if self.Debug then -if Coalition==coalition.side.RED then -self.airbase:GetCoordinate():SmokeRed() -elseif Coalition==coalition.side.BLUE then -self.airbase:GetCoordinate():SmokeBlue() -end -end -self.airbase=nil -end -function WAREHOUSE:onafterAirbaseRecaptured(From,Event,To,Coalition) -local text=string.format("Warehouse %s: We recaptured our airbase %s from the enemy (coalition=%d)!",self.alias,self.airbasename,Coalition) -self:_InfoMessage(text) -self.airbase=AIRBASE:FindByName(self.airbasename) -if self.Debug then -if Coalition==coalition.side.RED then -self.airbase:GetCoordinate():SmokeRed() -elseif Coalition==coalition.side.BLUE then -self.airbase:GetCoordinate():SmokeBlue() -end -end -end -function WAREHOUSE:onafterRunwayDestroyed(From,Event,To) -local text=string.format("Warehouse %s: Runway %s destroyed!",self.alias,self.airbasename) -self:_InfoMessage(text) -self.runwaydestroyed=timer.getAbsTime() -return self -end -function WAREHOUSE:onafterRunwayRepaired(From,Event,To) -local text=string.format("Warehouse %s: Runway %s repaired!",self.alias,self.airbasename) -self:_InfoMessage(text) -self.runwaydestroyed=nil -return self -end -function WAREHOUSE:onafterAssetSpawned(From,Event,To,group,asset,request) -local text=string.format("Asset %s from request id=%d was spawned!",asset.spawngroupname,request.uid) -self:T(self.lid..text) -asset.spawned=true -asset.spawngroupname=group:GetName() -self:_DeleteStockItem(asset) -if asset.iscargo==true then -request.cargogroupset=request.cargogroupset or SET_GROUP:New() -request.cargogroupset:AddGroup(group) -else -request.transportgroupset=request.transportgroupset or SET_GROUP:New() -request.transportgroupset:AddGroup(group) -end -group:SetState(group,"WAREHOUSE",self) -local n=0 -for _,_asset in pairs(request.assets)do -local assetitem=_asset -self:T(self.lid..string.format("Asset %s spawned %s as %s",assetitem.templatename,tostring(assetitem.spawned),tostring(assetitem.spawngroupname))) -if assetitem.spawned then -n=n+1 -else -end -end -if n==request.nasset+request.ntransport then -self:T(self.lid..string.format("All assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned. Calling RequestSpawned",n,request.nasset,request.ntransport,request.uid)) -self:RequestSpawned(request,request.cargogroupset,request.transportgroupset) -else -self:T(self.lid..string.format("Not all assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned YET",n,request.nasset,request.ntransport,request.uid)) -end -end -function WAREHOUSE:onafterAssetDead(From,Event,To,asset,request) -if asset and request then -local text=string.format("Asset %s from request id=%d is dead!",asset.templatename,request.uid) -self:T(self.lid..text) -local groupname=asset.spawngroupname -local NoTriggerEvent=true -if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then -if request.cargogroupset then -request.cargogroupset:Remove(groupname,NoTriggerEvent) -self:T(self.lid..string.format("Removed selfpropelled cargo %s: ncargo=%d.",groupname,request.cargogroupset:Count())) -else -self:E(self.lid..string.format("ERROR: cargogroupset is nil for request ID=%s!",tostring(request.uid))) -end -else -local istransport=not asset.iscargo -if istransport==true then -request.transportgroupset:Remove(groupname,NoTriggerEvent) -self:T(self.lid..string.format("Removed transport %s: ntransport=%d",groupname,request.transportgroupset:Count())) -elseif istransport==false then -request.cargogroupset:Remove(groupname,NoTriggerEvent) -self:T(self.lid..string.format("Removed transported cargo %s outside carrier: ncargo=%d",groupname,request.cargogroupset:Count())) -else -end -end -else -self:E(self.lid.."ERROR: Asset and/or Request is nil in onafterAssetDead") -end -end -function WAREHOUSE:onafterDestroyed(From,Event,To) -local text=string.format("Warehouse %s was destroyed! Assets lost %d. Respawn=%s",self.alias,#self.stock,tostring(self.respawnafterdestroyed)) -self:_InfoMessage(text) -if self.respawnafterdestroyed then -if self.respawndelay then -self:Pause() -self:__Respawn(self.respawndelay) -else -self:Respawn() -end -else -for k,_ in pairs(self.queue)do -self.queue[k]=nil -end -for k,_ in pairs(self.stock)do -end -for k=#self.stock,1,-1 do -self.stock[k]=nil -end -end -end -function WAREHOUSE:onafterSave(From,Event,To,path,filename) -local function _savefile(filename,data) -local f=assert(io.open(filename,"wb")) -f:write(data) -f:close() -end -filename=filename or string.format("WAREHOUSE-%d_%s.txt",self.uid,self.alias) -if path~=nil then -filename=path.."\\"..filename -end -local text=string.format("Saving warehouse assets to file %s",filename) -MESSAGE:New(text,30):ToAllIf(self.Debug or self.Report) -self:I(self.lid..text) -local warehouseassets="" -warehouseassets=warehouseassets..string.format("coalition=%d\n",self:GetCoalition()) -warehouseassets=warehouseassets..string.format("country=%d\n",self:GetCountry()) -for _,_asset in pairs(self.stock)do -local asset=_asset -local assetstring="" -for key,value in pairs(asset)do -if key=="templatename"or key=="attribute"or key=="cargobay"or key=="weight"or key=="loadradius"or key=="livery"or key=="skill"or key=="assignment"then -local name -if type(value)=="table"then -name=string.format("%s=%s;",key,value[1]) -else -name=string.format("%s=%s;",key,value) -end -assetstring=assetstring..name -end -self:I(string.format("Loaded asset: %s",assetstring)) -end -warehouseassets=warehouseassets..assetstring.."\n" -end -_savefile(filename,warehouseassets) -end -function WAREHOUSE:onbeforeLoad(From,Event,To,path,filename) -local function _fileexists(name) -local f=io.open(name,"r") -if f~=nil then -io.close(f) -return true -else -return false -end -end -filename=filename or string.format("WAREHOUSE-%d_%s.txt",self.uid,self.alias) -if path~=nil then -filename=path.."\\"..filename -end -local exists=_fileexists(filename) -if exists then -return true -else -self:_ErrorMessage(string.format("ERROR: file %s does not exist! Cannot load assets.",filename),60) -return false -end -end -function WAREHOUSE:onafterLoad(From,Event,To,path,filename) -local function _loadfile(filename) -local f=assert(io.open(filename,"rb")) -local data=f:read("*all") -f:close() -return data -end -filename=filename or string.format("WAREHOUSE-%d_%s.txt",self.uid,self.alias) -if path~=nil then -filename=path.."\\"..filename -end -local text=string.format("Loading warehouse assets from file %s",filename) -MESSAGE:New(text,30):ToAllIf(self.Debug or self.Report) -self:I(self.lid..text) -local data=_loadfile(filename) -local assetdata=UTILS.Split(data,"\n") -local Coalition -local Country -local assets={} -for _,asset in pairs(assetdata)do -local descriptors=UTILS.Split(asset,";") -local asset={} -local isasset=false -for _,descriptor in pairs(descriptors)do -local keyval=UTILS.Split(descriptor,"=") -if#keyval==2 then -if keyval[1]=="coalition"then -Coalition=tonumber(keyval[2]) -elseif keyval[1]=="country"then -Country=tonumber(keyval[2]) -else -isasset=true -local key=keyval[1] -local val=keyval[2] -if val=="nil"then -val=nil -end -if key=="cargobay"or key=="weight"or key=="loadradius"then -asset[key]=tonumber(val) -else -asset[key]=val -end -end -end -end -if isasset then -table.insert(assets,asset) -end -end -if Country~=self:GetCountry()then -self:T(self.lid..string.format("Changing warehouse country %d-->%d on loading assets.",self:GetCountry(),Country)) -self:ChangeCountry(Country) -end -for _,_asset in pairs(assets)do -local asset=_asset -local group=GROUP:FindByName(asset.templatename) -if group then -self:AddAsset(group,1,asset.attribute,asset.cargobay,asset.weight,asset.loadradius,asset.skill,asset.livery,asset.assignment) -else -self:E(string.format("ERROR: Group %s doest not exit. Cannot be loaded as asset.",tostring(asset.templatename))) -end -end -end -function WAREHOUSE:_SpawnAssetRequest(Request) -self:F2({requestUID=Request.uid}) -local cargoassets=Request.cargoassets -local Parking={} -if Request.cargocategory==Group.Category.AIRPLANE or Request.cargocategory==Group.Category.HELICOPTER then -Parking=self:_FindParkingForAssets(self.airbase,cargoassets)or{} -end -local UnControlled=true -for i=1,#cargoassets do -local asset=cargoassets[i] -if not asset.spawned then -asset.iscargo=true -asset.rid=Request.uid -local _alias=asset.spawngroupname -Request.assets[asset.uid]=asset -local _group=nil -if asset.category==Group.Category.GROUND then -_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.spawnzone,Request.lateActivation) -elseif asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then -if Parking[asset.uid]then -_group=self:_SpawnAssetAircraft(_alias,asset,Request,Parking[asset.uid],UnControlled,Request.lateActivation) -else -_group=self:_SpawnAssetAircraft(_alias,asset,Request,nil,UnControlled,Request.lateActivation) -end -elseif asset.category==Group.Category.TRAIN then -if self.rail then -_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.spawnzone,Request.lateActivation) -end -elseif asset.category==Group.Category.SHIP then -_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.portzone,Request.lateActivation) -else -self:E(self.lid.."ERROR: Unknown asset category!") -end -if _group then -self:__AssetSpawned(0.01,_group,asset,Request) -end -end -end -end -function WAREHOUSE:_SpawnAssetGroundNaval(alias,asset,request,spawnzone,lateactivated) -if asset and(asset.category==Group.Category.GROUND or asset.category==Group.Category.SHIP or asset.category==Group.Category.TRAIN)then -local template=self:_SpawnAssetPrepareTemplate(asset,alias) -template.route.points[1]={} -local coord=spawnzone:GetRandomCoordinate() -if asset.category==Group.Category.TRAIN then -coord=self.rail -end -for i=1,#template.units do -local unit=template.units[i] -local SX=unit.x or 0 -local SY=unit.y or 0 -local BX=asset.template.route.points[1].x -local BY=asset.template.route.points[1].y -local TX=coord.x+(SX-BX) -local TY=coord.z+(SY-BY) -template.units[i].x=TX -template.units[i].y=TY -if asset.livery then -unit.livery_id=asset.livery -end -if asset.skill then -unit.skill=asset.skill -end -end -template.lateActivation=lateactivated -template.route.points[1].x=coord.x -template.route.points[1].y=coord.z -template.x=coord.x -template.y=coord.z -template.alt=coord.y -local group=_DATABASE:Spawn(template) -return group -end -return nil -end -function WAREHOUSE:_SpawnAssetAircraft(alias,asset,request,parking,uncontrolled,lateactivated) -if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then -local template=self:_SpawnAssetPrepareTemplate(asset,alias) -local _type=COORDINATE.WaypointType.TakeOffParking -local _action=COORDINATE.WaypointAction.FromParkingArea -if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then -_type=COORDINATE.WaypointType.TakeOffParkingHot -_action=COORDINATE.WaypointAction.FromParkingAreaHot -uncontrolled=false -end -local airstart=asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TurningPoint or false -if airstart then -_type=COORDINATE.WaypointType.TurningPoint -_action=COORDINATE.WaypointAction.TurningPoint -uncontrolled=false -end -if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then -if request.toself then -local coord=self.airbase:GetCoordinate() -if airstart then -coord:SetAltitude(math.random(1000,2000)) -end -local wp=coord:WaypointAir("RADIO",_type,_action,0,false,self.airbase,{},"Parking") -template.route.points={wp} -else -template.route.points=self:_GetFlightplan(asset,self.airbase,request.warehouse.airbase) -end -else -template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO",_type,_action,0,true,self.airbase,nil,"Spawnpoint") -end -local AirbaseID=self.airbase:GetID() -local AirbaseCategory=self:GetAirbaseCategory() -if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then -else -if#parking<#template.units and not airstart then -local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.",#parking,#template.units) -self:_DebugMessage(text) -return nil -end -end -for i=1,#template.units do -local unit=template.units[i] -if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then -local coord=self.airbase:GetCoordinate() -unit.x=coord.x -unit.y=coord.z -unit.alt=coord.y -if airstart then -unit.alt=math.random(1000,2000) -end -unit.parking_id=nil -unit.parking=nil -else -local coord=nil -local terminal=nil -if airstart then -coord=self.airbase:GetCoordinate():SetAltitude(math.random(1000,2000)) -else -coord=parking[i].Coordinate -terminal=parking[i].TerminalID -end -if self.Debug then -local text=string.format("Spawnplace unit %s terminal %d.",unit.name,terminal) -coord:MarkToAll(text) -env.info(text) -end -unit.x=coord.x -unit.y=coord.z -unit.alt=coord.y -unit.parking_id=nil -unit.parking=terminal -end -if asset.livery then -unit.livery_id=asset.livery -end -if asset.skill then -unit.skill=asset.skill -end -if asset.payload then -unit.payload=asset.payload.pylons -end -if asset.modex then -unit.onboard_num=asset.modex[i] -end -if asset.callsign then -unit.callsign=asset.callsign[i] -end -end -template.x=template.units[1].x -template.y=template.units[1].y -template.uncontrolled=uncontrolled -self:T2({airtemplate=template}) -local group=_DATABASE:Spawn(template) -return group -end -return nil -end -function WAREHOUSE:_SpawnAssetPrepareTemplate(asset,alias) -local template=UTILS.DeepCopy(asset.template) -template.name=alias -template.CoalitionID=self:GetCoalition() -template.CountryID=self:GetCountry() -template.groupId=nil -template.lateActivation=false -if asset.missionTask then -self:T(self.lid..string.format("Setting mission task to %s",tostring(asset.missionTask))) -template.task=asset.missionTask -end -template.route={} -template.route.routeRelativeTOT=true -template.route.points={} -for i=1,#template.units do -local unit=template.units[i] -unit.unitId=nil -unit.name=string.format("%s-%02d",template.name,i) -end -return template -end -function WAREHOUSE:_RouteGround(group,request) -if group and group:IsAlive()then -local _speed=group:GetSpeedMax()*0.7 -local Waypoints={} -local hasoffroad=self:HasConnectionOffRoad(request.warehouse,self.Debug) -if hasoffroad then -local remotename=request.warehouse.warehouse:GetName() -local path=self.offroadpaths[remotename][math.random(#self.offroadpaths[remotename])] -for i=1,#path do -local coord=path[i] -local Waypoint=coord:WaypointGround(_speed,"Off Road") -table.insert(Waypoints,Waypoint) -end -else -Waypoints=group:TaskGroundOnRoad(request.warehouse.road,_speed,"Off Road",false,self.road) -local FromWP=group:GetCoordinate():WaypointGround(_speed,"Off Road") -table.insert(Waypoints,1,FromWP) -end -for n,wp in ipairs(Waypoints)do -local tf=self:_SimpleTaskFunctionWP("warehouse:_PassingWaypoint",group,n,#Waypoints) -group:SetTaskWaypoint(wp,tf) -end -group:Route(Waypoints,1) -group:OptionROEReturnFire() -group:OptionAlarmStateGreen() -end -end -function WAREHOUSE:_RouteNaval(group,request) -if group and group:IsAlive()then -local _speed=group:GetSpeedMax()*0.8 -local remotename=request.warehouse.warehouse:GetName() -local lane=self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])] -if lane then -local Waypoints={} -for i=1,#lane do -local coord=lane[i] -local Waypoint=coord:WaypointGround(_speed) -table.insert(Waypoints,Waypoint) -end -local TaskFunction=self:_SimpleTaskFunction("warehouse:_Arrived",group) -local Waypoint=Waypoints[#Waypoints] -group:SetTaskWaypoint(Waypoint,TaskFunction) -group:Route(Waypoints,1) -group:OptionROEReturnFire() -else -self:E(self.lid..string.format("ERROR: No shipping lane defined for Naval asset!")) -end -end -end -function WAREHOUSE:_RouteAir(aircraft) -if aircraft and aircraft:IsAlive()~=nil then -self:T2(self.lid..string.format("RouteAir aircraft group %s alive=%s",aircraft:GetName(),tostring(aircraft:IsAlive()))) -if self.flightcontrol then -local fg=FLIGHTGROUP:New(aircraft) -fg:SetReadyForTakeoff(true) -else -aircraft:StartUncontrolled(math.random(60)) -end -self:T2(self.lid..string.format("RouteAir aircraft group %s alive=%s (after start command)",aircraft:GetName(),tostring(aircraft:IsAlive()))) -aircraft:OptionROEReturnFire() -aircraft:OptionROTPassiveDefense() -else -self:E(string.format("ERROR: aircraft %s cannot be routed since it does not exist or is not alive %s!",tostring(aircraft:GetName()),tostring(aircraft:IsAlive()))) -end -end -function WAREHOUSE:_RouteTrain(Group,Coordinate,Speed) -if Group and Group:IsAlive()then -local _speed=Speed or Group:GetSpeedMax()*0.6 -local Waypoints=Group:TaskGroundOnRailRoads(Coordinate,Speed) -local TaskFunction=self:_SimpleTaskFunction("warehouse:_Arrived",Group) -local Waypoint=Waypoints[#Waypoints] -Group:SetTaskWaypoint(Waypoint,TaskFunction) -Group:Route(Waypoints,1) -end -end -function WAREHOUSE:_Arrived(group) -self:_DebugMessage(string.format("Group %s arrived!",tostring(group:GetName()))) -if group then -self:__Arrived(1,group) -end -end -function WAREHOUSE:_PassingWaypoint(group,n,N) -self:T(self.lid..string.format("Group %s passing waypoint %d of %d!",tostring(group:GetName()),n,N)) -if n==N then -self:__Arrived(1,group) -end -end -function WAREHOUSE:GetAssetByID(id) -if id then -return _WAREHOUSEDB.Assets[id] -else -return nil -end -end -function WAREHOUSE:GetAssetByName(GroupName) -local name=self:_GetNameWithOut(GroupName) -local _,aid,_=self:_GetIDsFromGroup(GROUP:FindByName(name)) -if aid then -return _WAREHOUSEDB.Assets[aid] -else -return nil -end -end -function WAREHOUSE:GetRequestByID(id) -if id then -for _,_request in pairs(self.queue)do -local request=_request -if request.uid==id then -return request,true -end -end -for _,_request in pairs(self.pending)do -local request=_request -if request.uid==id then -return request,false -end -end -end -return nil,nil -end -function WAREHOUSE:_OnEventBirth(EventData) -self:T3(self.lid..string.format("Warehouse %s (id=%s) captured event birth!",self.alias,self.uid)) -if EventData and EventData.IniGroup then -local group=EventData.IniGroup -local wid,aid,rid=self:_GetIDsFromGroup(group) -if wid==self.uid then -local asset=self:GetAssetByID(aid) -local request=self:GetRequestByID(rid) -if asset and request then -self:T(self.lid..string.format("Warehouse %s captured event birth of request ID=%d, asset ID=%d, unit %s spawned=%s",self.alias,request.uid,asset.uid,EventData.IniUnitName,tostring(asset.spawned))) -request.born=true -else -self:E(self.lid..string.format("ERROR: Either asset AID=%s or request RID=%s are nil in event birth of unit %s",tostring(aid),tostring(rid),tostring(EventData.IniUnitName))) -end -else -end -end -end -function WAREHOUSE:_OnEventEngineStartup(EventData) -self:T3(self.lid..string.format("Warehouse %s captured event engine startup!",self.alias)) -if EventData and EventData.IniGroup then -local group=EventData.IniGroup -local wid,aid,rid=self:_GetIDsFromGroup(group) -if wid==self.uid then -self:T(self.lid..string.format("Warehouse %s captured event engine startup of its asset unit %s.",self.alias,EventData.IniUnitName)) -end -end -end -function WAREHOUSE:_OnEventTakeOff(EventData) -self:T3(self.lid..string.format("Warehouse %s captured event takeoff!",self.alias)) -if EventData and EventData.IniGroup then -local group=EventData.IniGroup -local wid,aid,rid=self:_GetIDsFromGroup(group) -if wid==self.uid then -self:T(self.lid..string.format("Warehouse %s captured event takeoff of its asset unit %s.",self.alias,EventData.IniUnitName)) -end -end -end -function WAREHOUSE:_OnEventLanding(EventData) -self:T3(self.lid..string.format("Warehouse %s captured event landing!",self.alias)) -if EventData and EventData.IniGroup then -local group=EventData.IniGroup -local wid,aid,rid=self:_GetIDsFromGroup(group) -if wid~=nil and wid==self.uid then -self:T(self.lid..string.format("Warehouse %s captured event landing of its asset unit %s.",self.alias,EventData.IniUnitName)) -end -end -end -function WAREHOUSE:_OnEventEngineShutdown(EventData) -self:T3(self.lid..string.format("Warehouse %s captured event engine shutdown!",self.alias)) -if EventData and EventData.IniGroup then -local group=EventData.IniGroup -local wid,aid,rid=self:_GetIDsFromGroup(group) -if wid==self.uid then -self:T(self.lid..string.format("Warehouse %s captured event engine shutdown of its asset unit %s.",self.alias,EventData.IniUnitName)) -end -end -end -function WAREHOUSE:_OnEventArrived(EventData) -if EventData and EventData.IniUnit then -local unit=EventData.IniUnit -if unit and unit:IsAlive()==true and unit:InAir()==false then -local group=EventData.IniGroup -local wid,aid,rid=self:_GetIDsFromGroup(group) -if wid~=nil and aid~=nil and rid~=nil then -if self.uid==wid then -local request=self:_GetRequestOfGroup(group,self.pending) -if request then -local istransport=self:_GroupIsTransport(group,request) -local closest=group:GetCoordinate():GetClosestAirbase() -local rightairbase=closest:GetName()==request.warehouse:GetAirbase():GetName() -if istransport==false and rightairbase then -local nunits=#group:GetUnits() -local dt=10*(nunits-1)+1 -if self.verbosity>=1 then -local text=string.format("Air asset group %s from warehouse %s arrived at its destination. Trigger Arrived event in %d sec",group:GetName(),self.alias,dt) -self:_InfoMessage(text) -end -self:__Arrived(dt,group) -end -end -end -else -self:T3(string.format("Group that arrived did not belong to a warehouse. Warehouse ID=%s, Asset ID=%s, Request ID=%s.",tostring(wid),tostring(aid),tostring(rid))) -end -end -end -end -function WAREHOUSE:_OnEventCrashOrDead(EventData) -self:T3(self.lid..string.format("Warehouse %s captured event dead or crash!",self.alias)) -if EventData then -if EventData.IniUnitName then -local warehousename=self.warehouse:GetName() -if EventData.IniUnitName==warehousename then -self:_DebugMessage(string.format("Warehouse %s alias %s was destroyed!",warehousename,self.alias)) -self:Destroyed() -end -if self.airbase and self.airbasename and self.airbasename==EventData.IniUnitName then -if self:IsRunwayOperational()then -self:RunwayDestroyed() -else -self.runwaydestroyed=timer.getAbsTime() -end -end -end -self:T2(self.lid..string.format("Warehouse %s captured event dead or crash or unit %s",self.alias,tostring(EventData.IniUnitName))) -if EventData.IniGroup then -local group=EventData.IniGroup -local wid,aid,rid=self:_GetIDsFromGroup(group) -if wid==self.uid then -self:T(self.lid..string.format("Warehouse %s captured event dead or crash of its asset unit %s",self.alias,EventData.IniUnitName)) -for _,request in pairs(self.pending)do -local request=request -if request.uid==rid then -self:_UnitDead(EventData.IniUnit,EventData.IniGroup,request) -end -end -end -end -end -end -function WAREHOUSE:_UnitDead(deadunit,deadgroup,request) -self:F(self.lid.."FF unit dead "..deadunit:GetName()) -local opsgroup=_DATABASE:FindOpsGroup(deadgroup) -if opsgroup then -return nil -end -local nalive=deadgroup:CountAliveUnits() -local groupdead=false -if nalive>0 then -groupdead=false -else -groupdead=true -end -local asset=self:FindAssetInDB(deadgroup) -local unitname=self:_GetNameWithOut(deadunit) -local groupname=self:_GetNameWithOut(deadgroup) -if groupdead then -self:T(self.lid..string.format("Group %s (transport=%s) is dead!",groupname,tostring(self:_GroupIsTransport(deadgroup,request)))) -if self.Debug then -deadgroup:SmokeWhite() -end -self:AssetDead(asset,request) -end -local NoTriggerEvent=true -if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then -if not asset.iscargo then -local cargogroupnames=request.carriercargo[unitname] -if cargogroupnames then -for _,cargoname in pairs(cargogroupnames)do -request.cargogroupset:Remove(cargoname,NoTriggerEvent) -self:T(self.lid..string.format("Removed transported cargo %s inside dead carrier %s: ncargo=%d",cargoname,unitname,request.cargogroupset:Count())) -end -end -else -self:E(self.lid..string.format("ERROR: Group %s is neither cargo nor transport!",deadgroup:GetName())) -end -end -end -function WAREHOUSE:_OnEventBaseCaptured(EventData) -self:T3(self.lid..string.format("Warehouse %s captured event base captured!",self.alias)) -if self.airbasename==nil then -return -end -if EventData and EventData.Place then -local airbase=EventData.Place -if EventData.PlaceName==self.airbasename then -local NewCoalitionAirbase=airbase:GetCoalition() -self:T(self.lid..string.format("Airbase of warehouse %s (coalition ID=%d) was captured! New owner coalition ID=%d.",self.alias,self:GetCoalition(),NewCoalitionAirbase)) -if self.airbase==nil then -if NewCoalitionAirbase==self:GetCoalition()then -self:AirbaseRecaptured(NewCoalitionAirbase) -end -else -if NewCoalitionAirbase~=self:GetCoalition()then -self:AirbaseCaptured(NewCoalitionAirbase) -end -end -end -end -end -function WAREHOUSE:_OnEventMissionEnd(EventData) -self:T3(self.lid..string.format("Warehouse %s captured event mission end!",self.alias)) -if self.autosave then -self:Save(self.autosavepath,self.autosavefile) -end -end -function WAREHOUSE:_CheckConquered() -local coord=self.zone:GetCoordinate() -local radius=self.zone:GetRadius() -local gotunits,_,_,units,_,_=coord:ScanObjects(radius,true,false,false) -local Nblue=0 -local Nred=0 -local Nneutral=0 -local CountryBlue=nil -local CountryRed=nil -local CountryNeutral=nil -if gotunits then -for _,_unit in pairs(units)do -local unit=_unit -local distance=coord:Get2DDistance(unit:GetCoordinate()) -if unit:IsGround()and unit:IsAlive()and distance<=radius then -local _coalition=unit:GetCoalition() -local _country=unit:GetCountry() -self:T2(self.lid..string.format("Unit %s in warehouse zone of radius=%d m. Coalition=%d, country=%d. Distance = %d m.",unit:GetName(),radius,_coalition,_country,distance)) -if _coalition==coalition.side.BLUE then -Nblue=Nblue+1 -CountryBlue=_country -elseif _coalition==coalition.side.RED then -Nred=Nred+1 -CountryRed=_country -else -Nneutral=Nneutral+1 -CountryNeutral=_country -end -end -end -end -self:T(self.lid..string.format("Ground troops in warehouse zone: blue=%d, red=%d, neutral=%d",Nblue,Nred,Nneutral)) -local newcoalition=self:GetCoalition() -local newcountry=self:GetCountry() -if Nblue>0 and Nred==0 and Nneutral==0 then -newcoalition=coalition.side.BLUE -newcountry=CountryBlue -elseif Nblue==0 and Nred>0 and Nneutral==0 then -newcoalition=coalition.side.RED -newcountry=CountryRed -elseif Nblue==0 and Nred==0 and Nneutral>0 then -end -if self:IsAttacked()and newcoalition~=self:GetCoalition()then -self:Captured(newcoalition,newcountry) -return -end -if self:GetCoalition()==coalition.side.BLUE then -if self:IsRunning()and Nred>0 then -self:Attacked(coalition.side.RED,CountryRed) -end -if self:IsAttacked()and Nred==0 then -self:Defeated() -end -elseif self:GetCoalition()==coalition.side.RED then -if self:IsRunning()and Nblue>0 then -self:Attacked(coalition.side.BLUE,CountryBlue) -end -if self:IsAttacked()and Nblue==0 then -self:Defeated() -end -elseif self:GetCoalition()==coalition.side.NEUTRAL then -if self:IsRunning()and Nred>0 then -self:Attacked(coalition.side.RED,CountryRed) -elseif self:IsRunning()and Nblue>0 then -self:Attacked(coalition.side.BLUE,CountryBlue) -end -end -end -function WAREHOUSE:_CheckAirbaseOwner() -if self.airbasename then -local airbase=AIRBASE:FindByName(self.airbasename) -local airbasecurrentcoalition=airbase:GetCoalition() -if self.airbase then -if self:GetCoalition()~=airbasecurrentcoalition then -self.airbase=nil -end -else -if self:GetCoalition()==airbasecurrentcoalition then -self.airbase=airbase -end -end -end -end -function WAREHOUSE:_CheckRequestConsistancy(queue) -self:T3(self.lid..string.format("Number of queued requests = %d",#queue)) -local invalid={} -for _,_request in pairs(queue)do -local request=_request -self:T2(self.lid..string.format("Checking request id=%d.",request.uid)) -local valid=true -if request.nasset==0 then -self:E(self.lid..string.format("ERROR: INVALID request. Request for zero assets not possible. Can happen when, e.g. \"all\" ground assets are requests but none in stock.")) -valid=false -end -if self:GetCoalition()~=request.warehouse:GetCoalition()then -self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is of wrong coalition! Own coalition %s != %s of requesting warehouse.",self:GetCoalitionName(),request.warehouse:GetCoalitionName())) -valid=false -end -if request.warehouse:IsStopped()then -self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is stopped!")) -valid=false -end -if request.warehouse:IsDestroyed()and not self.respawnafterdestroyed then -self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is destroyed!")) -valid=false -end -if valid==false then -self:E(self.lid..string.format("Got invalid request id=%d.",request.uid)) -table.insert(invalid,request) -else -self:T3(self.lid..string.format("Got valid request id=%d.",request.uid)) -end -end -for _,_request in pairs(invalid)do -self:E(self.lid..string.format("Deleting INVALID request id=%d.",_request.uid)) -self:_DeleteQueueItem(_request,self.queue) -end -end -function WAREHOUSE:_CheckRequestValid(request) -local _assets,_nassets,_enough=self:_FilterStock(self.stock,request.assetdesc,request.assetdescval,request.nasset) -if#_assets==0 then -return true -end -local nasset=request.nasset -if type(request.nasset)=="string"then -nasset=self:_QuantityRel2Abs(request.nasset,_nassets) -end -local text=string.format("Request valid? Number of assets: requested=%s=%d, selected=%d, total=%d, enough=%s.",tostring(request.nasset),nasset,#_assets,_nassets,tostring(_enough)) -self:T(text) -local asset=_assets[1] -local asset_plane=asset.category==Group.Category.AIRPLANE -local asset_helo=asset.category==Group.Category.HELICOPTER -local asset_ground=asset.category==Group.Category.GROUND -local asset_train=asset.category==Group.Category.TRAIN -local asset_naval=asset.category==Group.Category.SHIP -local asset_air=asset_helo or asset_plane -local valid=true -local requestcategory=request.warehouse:GetAirbaseCategory() -if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then -if asset_air then -if asset_plane then -if requestcategory==Airbase.Category.HELIPAD or self:GetAirbaseCategory()==Airbase.Category.HELIPAD then -self:E("ERROR: Incorrect request. Asset airplane requested but warehouse or requestor is HELIPAD/FARP!") -valid=false -end -elseif asset_helo then -if self:GetAirbaseCategory()==-1 or requestcategory==-1 then -self:E("ERROR: Incorrect request. Helos need a AIRBASE/HELIPAD/SHIP as home/destination base!") -valid=false -end -end -if self.airbase==nil or request.airbase==nil then -self:E("ERROR: Incorrect request. Either warehouse or requesting warehouse does not have any kind of airbase!") -valid=false -else -local termtype_dep=asset.terminalType or self:_GetTerminal(asset.attribute,self:GetAirbaseCategory()) -local termtype_des=asset.terminalType or self:_GetTerminal(asset.attribute,request.warehouse:GetAirbaseCategory()) -local np_departure=self.airbase:GetParkingSpotsNumber(termtype_dep) -local np_destination=request.airbase:GetParkingSpotsNumber(termtype_des) -self:T(string.format("Asset attribute = %s, DEPARTURE: terminal type = %d, spots = %d, DESTINATION: terminal type = %d, spots = %d",asset.attribute,termtype_dep,np_departure,termtype_des,np_destination)) -if np_departure0 then -local asset=_assets[1] -_assetattribute=_assets[1].attribute -_assetcategory=_assets[1].category -_assetairstart=_assets[1].takeoffType and _assets[1].takeoffType==COORDINATE.WaypointType.TurningPoint or false -if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then -if self.airbase and self.airbase:GetCoalition()==self:GetCoalition()then -if self.airbase.storage then -local nS=self.airbase.storage:GetAmount(asset.unittype) -local nA=asset.nunits*request.nasset -if nS NOT enough to spawn the requested %d asset units (%d groups)", -self.alias,nS,asset.unittype,nA,request.nasset) -self:_InfoMessage(text,5) -return false -end -end -if self:IsRunwayOperational()or _assetairstart then -if _assetairstart then -else -local Parking=self:_FindParkingForAssets(self.airbase,_assets) -if Parking==nil then -local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.",self.alias) -self:_InfoMessage(text,5) -return false -end -end -else -local text=string.format("Warehouse %s: Request denied! Runway is still destroyed",self.alias) -self:_InfoMessage(text,5) -return false -end -else -local text=string.format("Warehouse %s: Request denied! No airbase",self.alias) -self:_InfoMessage(text,5) -return false -end -end -request.cargoassets=_assets -end -if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then -_transports=self:_GetTransportsForAssets(request) -if#_transports>0 then -local _transportattribute=_transports[1].attribute -local _transportcategory=_transports[1].category -if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then -if self.airbase and self.airbase:GetCoalition()==self:GetCoalition()then -if self:IsRunwayOperational()then -local Parking=self:_FindParkingForAssets(self.airbase,_transports) -if Parking==nil then -local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all transports at the moment.",self.alias) -self:_InfoMessage(text,5) -return false -end -else -local text=string.format("Warehouse %s: Request denied! Runway is still destroyed",self.alias) -self:_InfoMessage(text,5) -return false -end -else -local text=string.format("Warehouse %s: Request denied! No airbase currently!",self.alias) -self:_InfoMessage(text,5) -return false -end -end -else -local text=string.format("Warehouse %s: Request denied! Not enough transport carriers available at the moment.",self.alias) -self:_InfoMessage(text,5) -return false -end -else -if _assetcategory==Group.Category.GROUND then -local dist=self.warehouse:GetCoordinate():Get2DDistance(self.spawnzone:GetCoordinate()) -if dist>self.spawnzonemaxdist then -local text=string.format("Warehouse %s: Request denied! Not close enough to spawn zone. Distance = %d m. We need to be at least within %d m range to spawn.",self.alias,dist,self.spawnzonemaxdist) -self:_InfoMessage(text,5) -return false -end -elseif _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then -end -end -request.cargoassets=_assets -request.cargoattribute=_assets[1].attribute -request.cargocategory=_assets[1].category -request.nasset=#_assets -local text=string.format("Selected cargo assets, attibute=%s, category=%d:\n",request.cargoattribute,request.cargocategory) -for _i,_asset in pairs(_assets)do -local asset=_asset -text=text..string.format("%d) name=%s, type=%s, category=%d, #units=%d",_i,asset.templatename,asset.unittype,asset.category,asset.nunits) -end -self:T(self.lid..text) -if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then -request.transportassets=_transports -request.transportattribute=_transports[1].attribute -request.transportcategory=_transports[1].category -request.ntransport=#_transports -local text=string.format("Selected transport assets, attibute=%s, category=%d:\n",request.transportattribute,request.transportcategory) -for _i,_asset in pairs(_transports)do -local asset=_asset -text=text..string.format("%d) name=%s, type=%s, category=%d, #units=%d\n",_i,asset.templatename,asset.unittype,asset.category,asset.nunits) -end -self:T(self.lid..text) -end -return true -end -function WAREHOUSE:_GetTransportsForAssets(request) -local transports=self:_FilterStock(self.stock,WAREHOUSE.Descriptor.ATTRIBUTE,request.transporttype,nil,true) -local cargoassets=UTILS.DeepCopy(request.cargoassets) -local cargoset=request.transportcargoset -local function sort_transports(a,b) -return a.cargobaymax>b.cargobaymax -end -local function sort_cargoassets(a,b) -return a.weight>b.weight -end -table.sort(transports,sort_transports) -table.sort(cargoassets,sort_cargoassets) -self:T2(self.lid.."Transport capability:") -local totalbay=0 -for i=1,#transports do -local transport=transports[i] -for j=1,transport.nunits do -totalbay=totalbay+transport.cargobay[j] -self:T2(self.lid..string.format("Cargo bay = %d (unit=%d)",transport.cargobay[j],j)) -end -end -self:T2(self.lid..string.format("Total capacity = %d",totalbay)) -self:T2(self.lid.."Cargo weight:") -local totalcargoweight=0 -for i=1,#cargoassets do -local asset=cargoassets[i] -totalcargoweight=totalcargoweight+asset.weight -self:T2(self.lid..string.format("weight = %d",asset.weight)) -end -self:T2(self.lid..string.format("Total weight = %d",totalcargoweight)) -local used_transports={} -for i=1,#transports do -local transport=transports[i] -local putintocarrier={} -local used=false -for k=1,transport.nunits do -local cargobay=transport.cargobay[k] -for j,asset in pairs(cargoassets)do -local asset=asset -local delta=cargobay-asset.weight -if delta>=0 then -cargobay=cargobay-asset.weight -self:T3(self.lid..string.format("%s unit %d loads cargo uid=%d: bayempty=%02d, bayloaded = %02d - weight=%02d",transport.templatename,k,asset.uid,transport.cargobay[k],cargobay,asset.weight)) -table.insert(putintocarrier,j) -used=true -else -self:T2(self.lid..string.format("Carrier unit %s too small for cargo asset %s ==> cannot be used! Cargo bay - asset weight = %d kg",transport.templatename,asset.templatename,delta)) -end -end -end -for j=#putintocarrier,1,-1 do -local nput=putintocarrier[j] -local cargo=cargoassets[nput] -if cargo then -self:T2(self.lid..string.format("Cargo id=%d assigned for carrier id=%d",cargo.uid,transport.uid)) -table.remove(cargoassets,nput) -end -end -if used then -table.insert(used_transports,transport) -end -local ntrans=self:_QuantityRel2Abs(request.ntransport,#transports) -if#used_transports>=ntrans then -request.ntransport=#used_transports -break -end -end -local text=string.format("Used Transports for request %d to warehouse %s:\n",request.uid,request.warehouse.alias) -local totalcargobay=0 -for _i,_transport in pairs(used_transports)do -local transport=_transport -text=text..string.format("%d) %s: cargobay tot = %d kg, cargobay max = %d kg, nunits=%d\n",_i,transport.unittype,transport.cargobaytot,transport.cargobaymax,transport.nunits) -totalcargobay=totalcargobay+transport.cargobaytot -end -text=text..string.format("Total cargo bay capacity = %.1f kg\n",totalcargobay) -text=text..string.format("Total cargo weight = %.1f kg\n",totalcargoweight) -text=text..string.format("Minimum number of runs = %.1f",totalcargoweight/totalcargobay) -self:_DebugMessage(text) -return used_transports -end -function WAREHOUSE:_QuantityRel2Abs(relative,ntot) -local nabs=0 -if type(relative)=="string"then -if relative==WAREHOUSE.Quantity.ALL then -nabs=ntot -elseif relative==WAREHOUSE.Quantity.THREEQUARTERS then -nabs=UTILS.Round(ntot*3/4) -elseif relative==WAREHOUSE.Quantity.HALF then -nabs=UTILS.Round(ntot/2) -elseif relative==WAREHOUSE.Quantity.THIRD then -nabs=UTILS.Round(ntot/3) -elseif relative==WAREHOUSE.Quantity.QUARTER then -nabs=UTILS.Round(ntot/4) -else -nabs=math.min(1,ntot) -end -else -nabs=relative -end -self:T2(self.lid..string.format("Relative %s: tot=%d, abs=%.2f",tostring(relative),ntot,nabs)) -return nabs -end -function WAREHOUSE:_CheckQueue() -self:_SortQueue() -local request=nil -local invalid={} -local gotit=false -for _,_qitem in ipairs(self.queue)do -local qitem=_qitem -local valid=self:_CheckRequestValid(qitem) -local okay=false -if valid then -okay=self:_CheckRequestNow(qitem) -else -table.insert(invalid,qitem) -end -if okay and valid and not gotit then -request=qitem -gotit=true -break -end -end -for _,_request in pairs(invalid)do -self:T(self.lid..string.format("Deleting invalid request id=%d.",_request.uid)) -self:_DeleteQueueItem(_request,self.queue) -end -return request -end -function WAREHOUSE:_SimpleTaskFunction(Function,group) -self:F2({Function}) -local warehouse=self.warehouse:GetName() -local groupname=group:GetName() -local DCSScript={} -DCSScript[#DCSScript+1]=string.format('local mygroup = GROUP:FindByName(\"%s\") ',groupname) -if self.isUnit then -DCSScript[#DCSScript+1]=string.format("local mywarehouse = UNIT:FindByName(\"%s\") ",warehouse) -else -DCSScript[#DCSScript+1]=string.format("local mywarehouse = STATIC:FindByName(\"%s\") ",warehouse) -end -DCSScript[#DCSScript+1]=string.format('local warehouse = mywarehouse:GetState(mywarehouse, \"WAREHOUSE\") ') -DCSScript[#DCSScript+1]=string.format('%s(mygroup)',Function) -local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript))) -return DCSTask -end -function WAREHOUSE:_SimpleTaskFunctionWP(Function,group,n,N) -self:F2({Function}) -local warehouse=self.warehouse:GetName() -local groupname=group:GetName() -local DCSScript={} -DCSScript[#DCSScript+1]=string.format('local mygroup = GROUP:FindByName(\"%s\") ',groupname) -if self.isUnit then -DCSScript[#DCSScript+1]=string.format("local mywarehouse = UNIT:FindByName(\"%s\") ",warehouse) -else -DCSScript[#DCSScript+1]=string.format("local mywarehouse = STATIC:FindByName(\"%s\") ",warehouse) -end -DCSScript[#DCSScript+1]=string.format('local warehouse = mywarehouse:GetState(mywarehouse, \"WAREHOUSE\") ') -DCSScript[#DCSScript+1]=string.format('%s(mygroup, %d, %d)',Function,n,N) -local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript))) -return DCSTask -end -function WAREHOUSE:_GetTerminal(_attribute,_category) -local _terminal=AIRBASE.TerminalType.OpenBig -if _attribute==WAREHOUSE.Attribute.AIR_FIGHTER or _attribute==WAREHOUSE.Attribute.AIR_UAV then -_terminal=AIRBASE.TerminalType.FighterAircraft -elseif _attribute==WAREHOUSE.Attribute.AIR_BOMBER or _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTPLANE or _attribute==WAREHOUSE.Attribute.AIR_TANKER or _attribute==WAREHOUSE.Attribute.AIR_AWACS then -_terminal=AIRBASE.TerminalType.OpenBig -elseif _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO then -_terminal=AIRBASE.TerminalType.HelicopterUsable -else -end -if _category==Airbase.Category.SHIP then -if not(_attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO)then -_terminal=AIRBASE.TerminalType.OpenMedOrBig -end -end -return _terminal -end -function WAREHOUSE:_FindParkingForAssets(airbase,assets) -local scanradius=25 -local scanunits=true -local scanstatics=true -local scanscenery=false -local verysafe=false -local function _overlap(l1,l2,dist) -local safedist=(l1/2+l2/2)*1.05 -local safe=(dist>safedist) -self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s",l1,l2,safedist,dist,tostring(safe))) -return safe -end -local function _clients() -local coords={} -if not self.allowSpawnOnClientSpots then -local clients=_DATABASE.CLIENTS -for clientname,client in pairs(clients)do -local template=_DATABASE:GetGroupTemplateFromUnitName(clientname) -local units=template.units -for i,unit in pairs(units)do -local coord=COORDINATE:New(unit.x,unit.alt,unit.y) -coords[unit.name]=coord -end -end -end -return coords -end -local parkingdata=airbase.parking -local obstacles={} -self.clientcoords=self.clientcoords or _clients() -for clientname,_coord in pairs(self.clientcoords)do -table.insert(obstacles,{coord=_coord,size=15,name=clientname,type="client"}) -end -for _,parkingspot in pairs(parkingdata)do -local _spot=parkingspot.Coordinate -local _termid=parkingspot.TerminalID -local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius,scanunits,scanstatics,scanscenery) -for _,_unit in pairs(_units)do -local unit=_unit -local _coord=unit:GetVec3() -local _size=self:_GetObjectSize(unit:GetDCSObject()) -local _name=unit:GetName() -if unit and unit:IsAlive()then -table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="unit"}) -end -end -for _,static in pairs(_statics)do -local _coord=static:getPoint() -local _name=static:getName() -local _size=self:_GetObjectSize(static) -table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="static"}) -end -for _,scenery in pairs(_sceneries)do -local _coord=scenery:getPoint() -local _name=scenery:getTypeName() -local _size=self:_GetObjectSize(scenery) -table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="scenery"}) -end -end -local parking={} -for _,asset in pairs(assets)do -local _asset=asset -if not _asset.spawned then -local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute,self:GetAirbaseCategory()) -parking[_asset.uid]={} -for i=1,_asset.nunits do -local assetname=_asset.spawngroupname.."-"..tostring(i) -local gotit=false -for _,_parkingspot in pairs(parkingdata)do -local parkingspot=_parkingspot -local valid=true -if asset.parkingIDs then -valid=self:_CheckParkingAsset(parkingspot,asset) -else -local validTerminal=AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype) -local validParking=self:_CheckParkingValid(parkingspot) -local validBWlist=airbase:_CheckParkingLists(parkingspot.TerminalID) -valid=validTerminal and validParking and validBWlist -end -if valid then -local _spot=parkingspot.Coordinate -local _termid=parkingspot.TerminalID -local free=true -local problem=nil -for _,obstacle in pairs(obstacles)do -local dist=_spot:Get2DDistance(obstacle.coord) -local safe=_overlap(_asset.size,obstacle.size,dist) -if not safe then -self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE",assetname,_asset.uid,_termid,dist)) -free=false -problem=obstacle -problem.dist=dist -break -else -end -end -if free then -table.insert(parking[_asset.uid],parkingspot) -self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!",_termid,assetname,_asset.uid)) -table.insert(obstacles,{coord=_spot,size=_asset.size,name=assetname,type="asset"}) -gotit=true -break -else -if self.Debug then -local coord=problem.coord -local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.",problem.name,problem.type,_termid,problem.size,problem.dist) -self:I(self.lid..text) -coord:MarkToAll(string.format(text)) -else -self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!",_termid)) -end -end -else -self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported",parkingspot.TerminalID,parkingspot.TerminalType)) -end -end -if not gotit then -self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]",assetname,_asset.uid)) -return nil -end -end -end -end -return parking -end -function WAREHOUSE:_GetRequestOfGroup(group,queue) -local wid,aid,rid=self:_GetIDsFromGroup(group) -for _,_request in pairs(queue)do -local request=_request -if request.uid==rid then -return request -end -end -end -function WAREHOUSE:_GroupIsTransport(group,request) -local asset=self:FindAssetInDB(group) -if asset and asset.iscargo~=nil then -return not asset.iscargo -else -local groupname=self:_GetNameWithOut(group) -if request.transportgroupset then -local transporters=request.transportgroupset:GetSetObjects() -for _,transport in pairs(transporters)do -if transport:GetName()==groupname then -return true -end -end -end -if request.cargogroupset then -local cargos=request.cargogroupset:GetSetObjects() -for _,cargo in pairs(cargos)do -if self:_GetNameWithOut(cargo)==groupname then -return false -end -end -end -end -return nil -end -function WAREHOUSE:_GetNameWithOut(group) -local groupname=type(group)=="string"and group or group:GetName() -if groupname:find("CARGO")then -local name=groupname:gsub("#CARGO","") -return name -else -return groupname -end -end -function WAREHOUSE:_GetIDsFromGroup(group) -if group then -local groupname=group:GetName() -local wid,aid,rid=self:_GetIDsFromGroupName(groupname) -return wid,aid,rid -else -self:E("WARNING: Group not found in GetIDsFromGroup() function!") -end -end -function WAREHOUSE:_GetIDsFromGroupName(groupname) -local function analyse(text) -local unspawned=UTILS.Split(text,"#")[1] -local keywords=UTILS.Split(unspawned,"_") -local _wid=nil -local _aid=nil -local _rid=nil -for _,keys in pairs(keywords)do -local str=UTILS.Split(keys,"-") -local key=str[1] -local val=str[2] -if key:find("WID")then -_wid=tonumber(val) -elseif key:find("AID")then -_aid=tonumber(val) -elseif key:find("RID")then -_rid=tonumber(val) -end -end -return _wid,_aid,_rid -end -local wid,aid,rid=analyse(groupname) -local asset=self:GetAssetByID(aid) -if asset then -wid=asset.wid -rid=asset.rid -end -self:T3(self.lid..string.format("Group Name = %s",tostring(groupname))) -self:T3(self.lid..string.format("Warehouse ID = %s",tostring(wid))) -self:T3(self.lid..string.format("Asset ID = %s",tostring(aid))) -self:T3(self.lid..string.format("Request ID = %s",tostring(rid))) -return wid,aid,rid -end -function WAREHOUSE:FilterStock(descriptor,attribute,nmax,mobile) -return self:_FilterStock(self.stock,descriptor,attribute,nmax,mobile) -end -function WAREHOUSE:_FilterStock(stock,descriptor,attribute,nmax,mobile) -nmax=nmax or WAREHOUSE.Quantity.ALL -if mobile==nil then -mobile=false -end -local filtered={} -if descriptor==WAREHOUSE.Descriptor.ASSETLIST then -local ntot=0 -for _,_rasset in pairs(attribute)do -local rasset=_rasset -for _,_asset in ipairs(stock)do -local asset=_asset -if rasset.uid==asset.uid then -table.insert(filtered,asset) -break -end -end -end -return filtered,#filtered,#filtered>=#attribute -end -local ntot=0 -for _,_asset in ipairs(stock)do -local asset=_asset -local ismobile=asset.speedmax>0 -if asset[descriptor]==attribute then -if(mobile==true and ismobile)or mobile==false then -ntot=ntot+1 -end -end -end -if ntot==0 then -return filtered,ntot,false -end -nmax=self:_QuantityRel2Abs(nmax,ntot) -for _i,_asset in ipairs(stock)do -local asset=_asset -if asset[descriptor]==attribute then -if(mobile and asset.speedmax>0)or(not mobile)then -table.insert(filtered,asset) -if nmax~=nil and#filtered>=nmax then -return filtered,ntot,true -end -end -end -end -return filtered,ntot,ntot>=nmax -end -function WAREHOUSE:_HasAttribute(group,attribute) -if group then -local groupattribute=self:_GetAttribute(group) -return groupattribute==attribute -end -return false -end -function WAREHOUSE:_GetAttribute(group) -local attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN -if group then -local transportplane=group:HasAttribute("Transports")and group:HasAttribute("Planes") -local awacs=group:HasAttribute("AWACS") -local fighter=group:HasAttribute("Fighters")or group:HasAttribute("Interceptors")or group:HasAttribute("Multirole fighters")or(group:HasAttribute("Bombers")and not group:HasAttribute("Strategic bombers")) -local bomber=group:HasAttribute("Strategic bombers") -local tanker=group:HasAttribute("Tankers") -local uav=group:HasAttribute("UAVs") -local transporthelo=group:HasAttribute("Transport helicopters") -local attackhelicopter=group:HasAttribute("Attack helicopters") -local apc=group:HasAttribute("APC") -local truck=group:HasAttribute("Trucks")and group:GetCategory()==Group.Category.GROUND -local infantry=group:HasAttribute("Infantry") -local ifv=group:HasAttribute("IFV") -local artillery=group:HasAttribute("Artillery") -local tank=group:HasAttribute("Old Tanks")or group:HasAttribute("Modern Tanks") -local aaa=group:HasAttribute("AAA") -local ewr=group:HasAttribute("EWR") -local sam=group:HasAttribute("SAM elements")and(not group:HasAttribute("AAA")) -local train=group:GetCategory()==Group.Category.TRAIN -local aircraftcarrier=group:HasAttribute("Aircraft Carriers") -local warship=group:HasAttribute("Heavy armed ships") -local armedship=group:HasAttribute("Armed ships")or group:HasAttribute("Armed Ship") -local unarmedship=group:HasAttribute("Unarmed ships") -if transportplane then -attribute=WAREHOUSE.Attribute.AIR_TRANSPORTPLANE -elseif awacs then -attribute=WAREHOUSE.Attribute.AIR_AWACS -elseif fighter then -attribute=WAREHOUSE.Attribute.AIR_FIGHTER -elseif bomber then -attribute=WAREHOUSE.Attribute.AIR_BOMBER -elseif tanker then -attribute=WAREHOUSE.Attribute.AIR_TANKER -elseif transporthelo then -attribute=WAREHOUSE.Attribute.AIR_TRANSPORTHELO -elseif attackhelicopter then -attribute=WAREHOUSE.Attribute.AIR_ATTACKHELO -elseif uav then -attribute=WAREHOUSE.Attribute.AIR_UAV -elseif apc then -attribute=WAREHOUSE.Attribute.GROUND_APC -elseif ifv then -attribute=WAREHOUSE.Attribute.GROUND_IFV -elseif infantry then -attribute=WAREHOUSE.Attribute.GROUND_INFANTRY -elseif artillery then -attribute=WAREHOUSE.Attribute.GROUND_ARTILLERY -elseif tank then -attribute=WAREHOUSE.Attribute.GROUND_TANK -elseif aaa then -attribute=WAREHOUSE.Attribute.GROUND_AAA -elseif ewr then -attribute=WAREHOUSE.Attribute.GROUND_EWR -elseif sam then -attribute=WAREHOUSE.Attribute.GROUND_SAM -elseif truck then -attribute=WAREHOUSE.Attribute.GROUND_TRUCK -elseif train then -attribute=WAREHOUSE.Attribute.GROUND_TRAIN -elseif aircraftcarrier then -attribute=WAREHOUSE.Attribute.NAVAL_AIRCRAFTCARRIER -elseif warship then -attribute=WAREHOUSE.Attribute.NAVAL_WARSHIP -elseif armedship then -attribute=WAREHOUSE.Attribute.NAVAL_ARMEDSHIP -elseif unarmedship then -attribute=WAREHOUSE.Attribute.NAVAL_UNARMEDSHIP -else -if group:IsGround()then -attribute=WAREHOUSE.Attribute.GROUND_OTHER -elseif group:IsShip()then -attribute=WAREHOUSE.Attribute.NAVAL_OTHER -elseif group:IsAir()then -attribute=WAREHOUSE.Attribute.AIR_OTHER -else -attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN -end -end -end -return attribute -end -function WAREHOUSE:_GetObjectSize(DCSobject) -local DCSdesc=DCSobject:getDesc() -if DCSdesc.box then -local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) -local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) -local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) -return math.max(x,z),x,y,z -end -return 0,0,0,0 -end -function WAREHOUSE:GetStockInfo(stock) -local _data={} -for _j,_attribute in pairs(WAREHOUSE.Attribute)do -local n=0 -for _i,_item in pairs(stock)do -local _ite=_item -if _ite.attribute==_attribute then -n=n+1 -end -end -_data[_attribute]=n -end -return _data -end -function WAREHOUSE:_DeleteStockItem(stockitem) -for i=1,#self.stock do -local item=self.stock[i] -if item.uid==stockitem.uid then -table.remove(self.stock,i) -break -end -end -end -function WAREHOUSE:_DeleteQueueItem(qitem,queue) -self:F({qitem=qitem,queue=queue}) -for i=1,#queue do -local _item=queue[i] -if _item.uid==qitem.uid then -self:T(self.lid..string.format("Deleting queue item id=%d.",qitem.uid)) -table.remove(queue,i) -break -end -end -end -function WAREHOUSE:_DeleteQueueItemByID(qitemID,queue) -for i=1,#queue do -local _item=queue[i] -if _item.uid==qitemID then -self:T(self.lid..string.format("Deleting queue item id=%d.",qitemID)) -table.remove(queue,i) -break -end -end -end -function WAREHOUSE:_SortQueue() -self:F3() -local function _sort(a,b) -return(a.prio=2 then -local total="Empty" -if#queue>0 then -total=string.format("Total = %d",#queue) -end -local text=string.format("%s at %s: %s",name,self.alias,total) -for i,qitem in ipairs(queue)do -local qitem=qitem -local uid=qitem.uid -local prio=qitem.prio -local clock="N/A" -if qitem.timestamp then -clock=tostring(UTILS.SecondsToClock(qitem.timestamp)) -end -local assignment=tostring(qitem.assignment) -local requestor=qitem.warehouse.alias -local airbasename=qitem.warehouse:GetAirbaseName() -local requestorAirbaseCat=qitem.warehouse:GetAirbaseCategory() -local assetdesc=qitem.assetdesc -local assetdescval=qitem.assetdescval -if assetdesc==WAREHOUSE.Descriptor.ASSETLIST then -assetdescval="Asset list" -end -local nasset=tostring(qitem.nasset) -local ndelivered=tostring(qitem.ndelivered) -local ncargogroupset="N/A" -if qitem.cargogroupset then -ncargogroupset=tostring(qitem.cargogroupset:Count()) -end -local transporttype="N/A" -if qitem.transporttype then -transporttype=qitem.transporttype -end -local ntransport="N/A" -if qitem.ntransport then -ntransport=tostring(qitem.ntransport) -end -local ntransportalive="N/A" -if qitem.transportgroupset then -ntransportalive=tostring(qitem.transportgroupset:Count()) -end -local ntransporthome="N/A" -if qitem.ntransporthome then -ntransporthome=tostring(qitem.ntransporthome) -end -text=text..string.format( -"\n%d) UID=%d, Prio=%d, Clock=%s, Assignment=%s | Requestor=%s [Airbase=%s, category=%d] | Assets(%s)=%s: #requested=%s / #alive=%s / #delivered=%s | Transport=%s: #requested=%s / #alive=%s / #home=%s", -i,uid,prio,clock,assignment,requestor,airbasename,requestorAirbaseCat,assetdesc,assetdescval,nasset,ncargogroupset,ndelivered,transporttype,ntransport,ntransportalive,ntransporthome) -end -if#queue==0 then -self:I(self.lid..text) -else -if total~="Empty"then -self:I(self.lid..text) -end -end -end -end -function WAREHOUSE:_DisplayStatus() -if self.verbosity>=3 then -local text=string.format("\n------------------------------------------------------\n") -text=text..string.format("Warehouse %s status: %s\n",self.alias,self:GetState()) -text=text..string.format("------------------------------------------------------\n") -text=text..string.format("Coalition name = %s\n",self:GetCoalitionName()) -text=text..string.format("Country name = %s\n",self:GetCountryName()) -text=text..string.format("Airbase name = %s (category=%d)\n",self:GetAirbaseName(),self:GetAirbaseCategory()) -text=text..string.format("Queued requests = %d\n",#self.queue) -text=text..string.format("Pending requests = %d\n",#self.pending) -text=text..string.format("------------------------------------------------------\n") -text=text..self:_GetStockAssetsText() -self:I(text) -end -end -function WAREHOUSE:_GetStockAssetsText(messagetoall) -local _data=self:GetStockInfo(self.stock) -local text="Stock:\n" -local total=0 -for _attribute,_count in pairs(_data)do -if _count>0 then -local attribute=tostring(UTILS.Split(_attribute,"_")[2]) -text=text..string.format("%s = %d\n",attribute,_count) -total=total+_count -end -end -text=text..string.format("===================\n") -text=text..string.format("Total = %d\n",total) -text=text..string.format("------------------------------------------------------\n") -MESSAGE:New(text,10):ToAllIf(messagetoall) -return text -end -function WAREHOUSE:_UpdateWarehouseMarkText() -if self.markerOn then -local text=string.format("Warehouse state: %s\nTotal assets in stock %d:\n",self:GetState(),#self.stock) -for _attribute,_count in pairs(self:GetStockInfo(self.stock)or{})do -if _count>0 then -local attribute=tostring(UTILS.Split(_attribute,"_")[2]) -text=text..string.format("%s=%d, ",attribute,_count) -end -end -local coordinate=self:GetCoordinate() -local coalition=self:GetCoalition() -if not self.markerWarehouse then -self.markerWarehouse=MARKER:New(coordinate,text):ToCoalition(coalition) -else -local refresh=false -if self.markerWarehouse.text~=text then -self.markerWarehouse.text=text -refresh=true -end -if self.markerWarehouse.coordinate~=coordinate then -self.markerWarehouse.coordinate=coordinate -refresh=true -end -if self.markerWarehouse.coalition~=coalition then -self.markerWarehouse.coalition=coalition -refresh=true -end -if refresh then -self.markerWarehouse:Refresh() -end -end -end -end -function WAREHOUSE:_DisplayStockItems(stock) -local text=self.lid..string.format("Warehouse %s stock assets:",self.alias) -for _i,_stock in pairs(stock)do -local mystock=_stock -local name=mystock.templatename -local category=mystock.category -local cargobaymax=mystock.cargobaymax -local cargobaytot=mystock.cargobaytot -local nunits=mystock.nunits -local range=mystock.range -local size=mystock.size -local speed=mystock.speedmax -local uid=mystock.uid -local unittype=mystock.unittype -local weight=mystock.weight -local attribute=mystock.attribute -text=text..string.format("\n%02d) uid=%d, name=%s, unittype=%s, category=%d, attribute=%s, nunits=%d, speed=%.1f km/h, range=%.1f km, size=%.1f m, weight=%.1f kg, cargobax max=%.1f kg tot=%.1f kg", -_i,uid,name,unittype,category,attribute,nunits,speed,range/1000,size,weight,cargobaymax,cargobaytot) -end -self:T3(text) -end -function WAREHOUSE:_Fireworks(coord) -coord=coord or self:GetCoordinate() -for i=1,91 do -local color=math.random(0,3) -coord:Flare(color,i-1) -end -end -function WAREHOUSE:_InfoMessage(text,duration) -duration=duration or 20 -if duration>0 and self.Debug or self.Report then -MESSAGE:New(text,duration):ToCoalition(self:GetCoalition()) -end -self:I(self.lid..text) -end -function WAREHOUSE:_DebugMessage(text,duration) -duration=duration or 20 -if self.Debug and duration>0 then -MESSAGE:New(text,duration):ToAllIf(self.Debug) -end -self:T(self.lid..text) -end -function WAREHOUSE:_ErrorMessage(text,duration) -duration=duration or 20 -if duration>0 then -MESSAGE:New(text,duration):ToAll() -end -self:E(self.lid..text) -end -function WAREHOUSE:_GetMaxHeight(D,alphaC,alphaD,Hdep,Hdest,Deltahhold) -local Hhold=Hdest+Deltahhold -local hdest=Hdest-Hdep -local hhold=hdest+Deltahhold -local Dp=math.sqrt(D^2+hhold^2) -local alphaS=math.atan(hdest/D) -local alphaH=math.atan(hhold/D) -local alphaCp=alphaC-alphaH -local alphaDp=alphaD+alphaH -local gammap=math.pi-alphaCp-alphaDp -local sCp=Dp*math.sin(alphaDp)/math.sin(gammap) -local sDp=Dp*math.sin(alphaCp)/math.sin(gammap) -local hmax=sCp*math.sin(alphaC) -if self.Debug then -env.info(string.format("Hdep = %.3f km",Hdep/1000)) -env.info(string.format("Hdest = %.3f km",Hdest/1000)) -env.info(string.format("DetaHold= %.3f km",Deltahhold/1000)) -env.info() -env.info(string.format("D = %.3f km",D/1000)) -env.info(string.format("Dp = %.3f km",Dp/1000)) -env.info() -env.info(string.format("alphaC = %.3f Deg",math.deg(alphaC))) -env.info(string.format("alphaCp = %.3f Deg",math.deg(alphaCp))) -env.info() -env.info(string.format("alphaD = %.3f Deg",math.deg(alphaD))) -env.info(string.format("alphaDp = %.3f Deg",math.deg(alphaDp))) -env.info() -env.info(string.format("alphaS = %.3f Deg",math.deg(alphaS))) -env.info(string.format("alphaH = %.3f Deg",math.deg(alphaH))) -env.info() -env.info(string.format("sCp = %.3f km",sCp/1000)) -env.info(string.format("sDp = %.3f km",sDp/1000)) -env.info() -env.info(string.format("hmax = %.3f km",hmax/1000)) -env.info() -local hdescent=hmax-hhold -local dClimb=hmax/math.tan(alphaC) -local dDescent=(hmax-hhold)/math.tan(alphaD) -local dCruise=D-dClimb-dDescent -env.info(string.format("hmax = %.3f km",hmax/1000)) -env.info(string.format("hdescent = %.3f km",hdescent/1000)) -env.info(string.format("Dclimb = %.3f km",dClimb/1000)) -env.info(string.format("Dcruise = %.3f km",dCruise/1000)) -env.info(string.format("Ddescent = %.3f km",dDescent/1000)) -env.info() -end -return hmax -end -function WAREHOUSE:_GetFlightplan(asset,departure,destination) -local Vmax=asset.speedmax/3.6 -local Range=asset.range -local category=asset.category -local ceiling=asset.DCSdesc.Hmax -local Vymax=asset.DCSdesc.VyMax -local VxCruiseMax=0.90*Vmax -local VxCruiseMin=math.min(VxCruiseMax*0.70,166) -local VxCruise=UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin,(VxCruiseMax-VxCruiseMax)/4,VxCruiseMin,VxCruiseMax) -local VxClimb=math.min(Vmax*0.90,200) -local VxDescent=math.min(Vmax*0.60,140) -local VxHolding=VxDescent*0.9 -local VxFinal=VxHolding*0.9 -local VyClimb=math.min(7.6,Vymax) -local AlphaClimb=math.rad(4) -local AlphaDescent=math.rad(4) -local FLcruise_expect=150*RAT.unit.FL2m -if category==Group.Category.HELICOPTER then -FLcruise_expect=1000 -end -local Pdeparture=departure:GetCoordinate() -local H_departure=Pdeparture.y -local Pdestination=destination:GetCoordinate() -local H_destination=Pdestination.y -local Rhmin=5000 -local Rhmax=10000 -if category==Group.Category.HELICOPTER then -Rhmin=500 -Rhmax=1000 -end -local Pholding=Pdestination:GetRandomCoordinateInRadius(Rhmax,Rhmin) -local d_holding=Pholding:Get2DDistance(Pdestination) -local H_holding=Pholding.y -local heading=Pdeparture:HeadingTo(Pholding) -local d_total=Pdeparture:Get2DDistance(Pholding) -local h_holding=1200 -if category==Group.Category.HELICOPTER then -h_holding=150 -end -h_holding=UTILS.Randomize(h_holding,0.2) -local DeltaholdingMax=self:_GetMaxHeight(d_total,AlphaClimb,AlphaDescent,H_departure,H_holding,0) -if h_holding>DeltaholdingMax then -h_holding=math.abs(DeltaholdingMax) -end -local Hh_holding=H_holding+h_holding -local h_max=self:_GetMaxHeight(d_total,AlphaClimb,AlphaDescent,H_departure,H_holding,h_holding) -local FLmax=h_max+H_departure -local FLmin=math.max(H_departure,Hh_holding) -FLmax=math.min(FLmax,ceiling) -if FLmin>FLmax then -FLmin=FLmax -end -if FLcruise_expectFLmax then -FLcruise_expect=FLmax -end -local FLcruise=UTILS.RandomGaussian(FLcruise_expect,math.abs(FLmax-FLmin)/4,FLmin,FLmax) -local h_climb=FLcruise-H_departure -local h_descent=FLcruise-Hh_holding -local d_climb=h_climb/math.tan(AlphaClimb) -local d_descent=h_descent/math.tan(AlphaDescent) -local d_cruise=d_total-d_climb-d_descent -local text=string.format("Flight plan:\n") -text=text..string.format("Vx max = %.2f km/h\n",Vmax*3.6) -text=text..string.format("Vx climb = %.2f km/h\n",VxClimb*3.6) -text=text..string.format("Vx cruise = %.2f km/h\n",VxCruise*3.6) -text=text..string.format("Vx descent = %.2f km/h\n",VxDescent*3.6) -text=text..string.format("Vx holding = %.2f km/h\n",VxHolding*3.6) -text=text..string.format("Vx final = %.2f km/h\n",VxFinal*3.6) -text=text..string.format("Vy max = %.2f m/s\n",Vymax) -text=text..string.format("Vy climb = %.2f m/s\n",VyClimb) -text=text..string.format("Alpha Climb = %.2f Deg\n",math.deg(AlphaClimb)) -text=text..string.format("Alpha Descent = %.2f Deg\n",math.deg(AlphaDescent)) -text=text..string.format("Dist climb = %.3f km\n",d_climb/1000) -text=text..string.format("Dist cruise = %.3f km\n",d_cruise/1000) -text=text..string.format("Dist descent = %.3f km\n",d_descent/1000) -text=text..string.format("Dist total = %.3f km\n",d_total/1000) -text=text..string.format("h_climb = %.3f km\n",h_climb/1000) -text=text..string.format("h_desc = %.3f km\n",h_descent/1000) -text=text..string.format("h_holding = %.3f km\n",h_holding/1000) -text=text..string.format("h_max = %.3f km\n",h_max/1000) -text=text..string.format("FL min = %.3f km\n",FLmin/1000) -text=text..string.format("FL expect = %.3f km\n",FLcruise_expect/1000) -text=text..string.format("FL cruise * = %.3f km\n",FLcruise/1000) -text=text..string.format("FL max = %.3f km\n",FLmax/1000) -text=text..string.format("Ceiling = %.3f km\n",ceiling/1000) -text=text..string.format("Max range = %.3f km\n",Range/1000) -self:T(self.lid..text) -if d_cruise<0 then -d_cruise=100 -end -local wp={} -local c={} -local _type=COORDINATE.WaypointType.TakeOffParking -local _action=COORDINATE.WaypointAction.FromParkingArea -if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then -_type=COORDINATE.WaypointType.TakeOffParkingHot -_action=COORDINATE.WaypointAction.FromParkingAreaHot -else -end -c[#c+1]=Pdeparture -wp[#wp+1]=Pdeparture:WaypointAir("RADIO",_type,_action,VxClimb*3.6,true,departure,nil,"Departure") -local Pcruise=Pdeparture:Translate(d_climb,heading) -Pcruise.y=FLcruise -c[#c+1]=Pcruise -wp[#wp+1]=Pcruise:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,VxCruise*3.6,true,nil,nil,"Cruise") -local Pdescent=Pcruise:Translate(d_cruise,heading) -Pdescent.y=FLcruise -c[#c+1]=Pdescent -wp[#wp+1]=Pdescent:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,VxDescent*3.6,true,nil,nil,"Descent") -Pholding.y=H_holding+h_holding -c[#c+1]=Pholding -wp[#wp+1]=Pholding:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,VxHolding*3.6,true,nil,nil,"Holding") -c[#c+1]=Pdestination -wp[#wp+1]=Pdestination:WaypointAir("RADIO",COORDINATE.WaypointType.Land,COORDINATE.WaypointAction.Landing,VxFinal*3.6,true,destination,nil,"Final Destination") -if self.Debug then -for i,coord in pairs(c)do -local coord=coord -local dist=0 -if i>1 then -dist=coord:Get2DDistance(c[i-1]) -end -coord:MarkToAll(string.format("Waypoint %i, distance = %.2f km",i,dist/1000)) -end -end -return wp,c -end -FOX={ -ClassName="FOX", -verbose=0, -Debug=false, -lid=nil, -menuadded={}, -menudisabled=nil, -destroy=nil, -launchalert=nil, -marklaunch=nil, -missiles={}, -players={}, -safezones={}, -launchzones={}, -protectedset=nil, -explosionpower=0.1, -explosiondist=200, -explosiondist2=500, -bigmissilemass=50, -destroy=nil, -dt50=5, -dt10=1, -dt05=0.5, -dt01=0.1, -dt00=0.01, -} -FOX.MenuF10={} -FOX.MenuF10Root=nil -FOX.version="0.8.0" -function FOX:New() -self.lid="FOX | " -local self=BASE:Inherit(self,FSM:New()) -self:SetDefaultMissileDestruction(true) -self:SetDefaultLaunchAlerts(true) -self:SetDefaultLaunchMarks(true) -self:SetExplosionDistance() -self:SetExplosionDistanceBigMissiles() -self:SetExplosionPower() -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","Status","*") -self:AddTransition("*","MissileLaunch","*") -self:AddTransition("*","MissileDestroyed","*") -self:AddTransition("*","EnterSafeZone","*") -self:AddTransition("*","ExitSafeZone","*") -self:AddTransition("Running","Stop","Stopped") -return self -end -function FOX:onafterStart(From,Event,To) -local text=string.format("Starting FOX Missile Trainer %s",FOX.version) -env.info(text) -self:HandleEvent(EVENTS.Birth) -self:HandleEvent(EVENTS.Shot) -if self.Debug then -self:HandleEvent(EVENTS.Hit) -end -if self.Debug then -self:TraceClass(self.ClassName) -self:TraceLevel(2) -end -self:__Status(-20) -end -function FOX:onafterStop(From,Event,To) -local text=string.format("Stopping FOX Missile Trainer %s",FOX.version) -env.info(text) -self:UnHandleEvent(EVENTS.Birth) -self:UnHandleEvent(EVENTS.Shot) -if self.Debug then -self:UnhandleEvent(EVENTS.Hit) -end -end -function FOX:AddSafeZone(zone) -table.insert(self.safezones,zone) -return self -end -function FOX:AddLaunchZone(zone) -table.insert(self.launchzones,zone) -return self -end -function FOX:SetProtectedGroupSet(groupset) -self.protectedset=groupset -return self -end -function FOX:AddProtectedGroup(group) -if not self.protectedset then -self.protectedset=SET_GROUP:New() -end -self.protectedset:AddGroup(group) -return self -end -function FOX:SetExplosionPower(power) -self.explosionpower=power or 0.1 -return self -end -function FOX:SetExplosionDistance(distance) -self.explosiondist=distance or 200 -return self -end -function FOX:SetExplosionDistanceBigMissiles(distance,explosivemass) -self.explosiondist2=distance or 500 -self.bigmissilemass=explosivemass or 50 -return self -end -function FOX:SetDisableF10Menu() -self.menudisabled=true -return self -end -function FOX:SetEnableF10Menu() -self.menudisabled=false -return self -end -function FOX:SetVerbosity(VerbosityLevel) -self.verbose=VerbosityLevel or 0 -return self -end -function FOX:SetDefaultMissileDestruction(switch) -if switch==nil then -self.destroy=false -else -self.destroy=switch -end -return self -end -function FOX:SetDefaultLaunchAlerts(switch) -if switch==nil then -self.launchalert=false -else -self.launchalert=switch -end -return self -end -function FOX:SetDefaultLaunchMarks(switch) -if switch==nil then -self.marklaunch=false -else -self.marklaunch=switch -end -return self -end -function FOX:SetDebugOnOff(switch) -if switch==nil then -self.Debug=false -else -self.Debug=switch -end -return self -end -function FOX:SetDebugOn() -self:SetDebugOnOff(true) -return self -end -function FOX:SetDebugOff() -self:SetDebugOff(false) -return self -end -function FOX:onafterStatus(From,Event,To) -local fsmstate=self:GetState() -local time=timer.getAbsTime() -local clock=UTILS.SecondsToClock(time) -if self.verbose>=1 then -self:I(self.lid..string.format("Missile trainer status %s: %s",clock,fsmstate)) -end -self:_CheckMissileStatus() -self:_CheckPlayers() -if fsmstate=="Running"then -self:__Status(-10) -end -end -function FOX:_CheckPlayers() -for playername,_playersettings in pairs(self.players)do -local playersettings=_playersettings -local unitname=playersettings.unitname -local unit=UNIT:FindByName(unitname) -if unit and unit:IsAlive()then -local coord=unit:GetCoordinate() -local issafe=self:_CheckCoordSafe(coord) -if issafe then -if not playersettings.inzone then -self:EnterSafeZone(playersettings) -playersettings.inzone=true -end -else -if playersettings.inzone==true then -self:ExitSafeZone(playersettings) -playersettings.inzone=false -end -end -end -end -end -function FOX:_RemoveMissile(missile) -if missile then -for i,_missile in pairs(self.missiles)do -local m=_missile -if missile.missileName==m.missileName then -table.remove(self.missiles,i) -return -end -end -end -end -function FOX:_CheckMissileStatus() -local text="Missiles:" -local inactive={} -for i,_missile in pairs(self.missiles)do -local missile=_missile -local targetname="unkown" -if missile.targetUnit then -targetname=missile.targetUnit:GetName() -end -local playername="none" -if missile.targetPlayer then -playername=missile.targetPlayer.name -end -local active=tostring(missile.active) -local mtype=missile.missileType -local dtype=missile.missileType -local range=UTILS.MetersToNM(missile.missileRange) -if not active then -table.insert(inactive,i) -end -local heading=self:_GetWeapongHeading(missile.weapon) -text=text..string.format("\n[%d] %s: active=%s, range=%.1f NM, heading=%03d, target=%s, player=%s, missilename=%s",i,mtype,active,range,heading,targetname,playername,missile.missileName) -end -if#self.missiles==0 then -text=text.." none" -end -if self.verbose>=2 then -self:I(self.lid..text) -end -for i=#self.missiles,1,-1 do -local missile=self.missiles[i] -if missile and not missile.active then -table.remove(self.missiles,i) -end -end -end -function FOX:_IsProtected(targetunit) -if not self.protectedset then -return false -end -if targetunit and targetunit:IsAlive()then -local targetgroup=targetunit:GetGroup() -if targetgroup then -local targetname=targetgroup:GetName() -for _,_group in pairs(self.protectedset:GetSet())do -local group=_group -if group then -local groupname=group:GetName() -if targetname==groupname then -return true -end -end -end -end -end -return false -end -function FOX._FuncTrack(weapon,self,missile) -local missileCoord=missile.missileCoord:UpdateFromVec3(weapon.vec3) -local missileVelocity=weapon:GetSpeed() -self:GetMissileTarget(missile) -local target=nil -if missile.targetUnit then -if missile.targetPlayer then -if missile.targetPlayer.destroy==true then -target=missile.targetUnit -end -else -if self:_IsProtected(missile.targetUnit)then -target=missile.targetUnit -end -end -else -local function _GetTarget(_unit) -local unit=_unit -local playerCoord=unit:GetCoordinate() -local dist=missileCoord:Get3DDistance(playerCoord) -if dist<=self.explosiondist then -return unit -end -end -local mindist=nil -for _,_player in pairs(self.players)do -local player=_player -if player.unitname~=missile.shooterName then -local playerCoord=player.unit:GetCoordinate() -local dist=missileCoord:Get3DDistance(playerCoord) -local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord) -if(mindist==nil or dist=self.bigmissilemass -end -if destroymissile and self:_CheckCoordSafe(targetVec3)then -self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m", -missile.missileType,missile.missileName,missile.shooterName,target:GetName(),tostring(missile.targetPlayer~=nil),distance)) -weapon:Destroy() -missile.active=false -if self.Debug then -missileCoord:SmokeRed() -end -self:MissileDestroyed(missile) -if self.explosionpower>0 and distance>50 and(distShooter==nil or(distShooter and distShooter>50))then -missileCoord:Explosion(self.explosionpower) -end -if missile.targetPlayer then -local text=string.format("Destroying missile. %s",self:_DeadText()) -MESSAGE:New(text,10):ToGroup(target:GetGroup()) -missile.targetPlayer.dead=missile.targetPlayer.dead+1 -end -else -local dt=1.0 -if distance>50000 then -dt=self.dt50 -elseif distance>10000 then -dt=self.dt10 -elseif distance>5000 then -dt=self.dt05 -elseif distance>1000 then -dt=self.dt01 -else -dt=self.dt00 -end -weapon:SetTimeStepTrack(dt) -end -else -self:T(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.",missile.missileType,missile.missileName,missile.shooterName)) -weapon:SetTimeStepTrack(0.1) -end -end -function FOX._FuncImpact(weapon,self,missile) -if missile.targetPlayer then -local player=missile.targetPlayer -if player and player.unit:IsAlive()then -local text=string.format("Missile defeated. Well done, %s!",player.name) -MESSAGE:New(text,10):ToClient(player.client) -player.defeated=player.defeated+1 -end -end -missile.active=false -self:T(FOX.lid..string.format("Terminating missile track timer.")) -weapon.tracking=false -end -function FOX:onafterMissileLaunch(From,Event,To,missile) -local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s",missile.missileType,missile.missileName,tostring(missile.targetName),missile.shooterName) -self:I(FOX.lid..text) -MESSAGE:New(text,10):ToAllIf(self.Debug) -for _,_player in pairs(self.players)do -local player=_player -local playerUnit=player.unit -if playerUnit and playerUnit:IsAlive()and player.coalition~=missile.shooterCoalition then -local distance=playerUnit:GetCoordinate():Get3DDistance(missile.shotCoord) -local bearing=playerUnit:GetCoordinate():HeadingTo(missile.shotCoord) -if player.launchalert then -if(missile.targetPlayer and player.unitname==missile.targetPlayer.unitname)or(distance Target=%s, fuse dist=%s, explosive=%s", -tostring(missile.shooterName),tostring(missile.missileType),tostring(missile.missileName),tostring(missile.targetName),tostring(missile.fuseDist),tostring(missile.explosive))) -if missile.targetPlayer or self:_IsProtected(missile.targetUnit)or missile.targetName=="unknown"then -table.insert(self.missiles,missile) -self:__MissileLaunch(0.1,missile) -end -end -end -function FOX:OnEventHit(EventData) -self:T({eventhit=EventData}) -if EventData.Weapon==nil then -return -end -if EventData.IniUnit==nil then -return -end -if EventData.TgtUnit==nil then -return -end -local weapon=EventData.Weapon -local weaponname=weapon:getName() -for i,_missile in pairs(self.missiles)do -local missile=_missile -if missile.missileName==weaponname then -self:I(self.lid..string.format("WARNING: Missile %s (%s) hit target %s. Missile trainer target was %s.",missile.missileType,missile.missileName,EventData.TgtUnitName,missile.targetName)) -self:I({missile=missile}) -return -end -end -end -function FOX:_AddF10Commands(_unitName) -self:F(_unitName) -local _unit,playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and playername then -local group=_unit:GetGroup() -local gid=group:GetID() -if group and gid then -if not self.menuadded[gid]then -self.menuadded[gid]=true -local _rootPath=nil -if FOX.MenuF10Root then -_rootPath=FOX.MenuF10Root -else -if FOX.MenuF10[gid]==nil then -FOX.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid,"FOX") -end -_rootPath=FOX.MenuF10[gid] -end -missionCommands.addCommandForGroup(gid,"Destroy Missiles On/Off",_rootPath,self._ToggleDestroyMissiles,self,_unitName) -missionCommands.addCommandForGroup(gid,"Launch Alerts On/Off",_rootPath,self._ToggleLaunchAlert,self,_unitName) -missionCommands.addCommandForGroup(gid,"Mark Launch On/Off",_rootPath,self._ToggleLaunchMark,self,_unitName) -missionCommands.addCommandForGroup(gid,"My Status",_rootPath,self._MyStatus,self,_unitName) -end -else -self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.",_unitName or"unknown")) -end -else -self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.",_unitName or"unknown")) -end -end -function FOX:_MyStatus(_unitname) -self:F2(_unitname) -local unit,playername=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local playerData=self.players[playername] -if playerData then -local m,mtext=self:_GetTargetMissiles(playerData.name) -local text=string.format("Status of player %s:\n",playerData.name) -local safe=self:_CheckCoordSafe(playerData.unit:GetCoordinate()) -text=text..string.format("Destroy missiles? %s\n",tostring(playerData.destroy)) -text=text..string.format("Launch alert? %s\n",tostring(playerData.launchalert)) -text=text..string.format("Launch marks? %s\n",tostring(playerData.marklaunch)) -text=text..string.format("Am I safe? %s\n",tostring(safe)) -text=text..string.format("Missiles defeated: %d\n",playerData.defeated) -text=text..string.format("Missiles destroyed: %d\n",playerData.dead) -text=text..string.format("Me target: %d\n%s",m,mtext) -MESSAGE:New(text,10,nil,true):ToClient(playerData.client) -end -end -end -function FOX:_GetTargetMissiles(playername) -local text="" -local n=0 -for _,_missile in pairs(self.missiles)do -local missile=_missile -if missile.targetPlayer and missile.targetPlayer.name==playername then -n=n+1 -text=text..string.format("Type %s: active %s\n",missile.missileType,tostring(missile.active)) -end -end -return n,text -end -function FOX:_ToggleLaunchAlert(_unitname) -self:F2(_unitname) -local unit,playername=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local playerData=self.players[playername] -if playerData then -playerData.launchalert=not playerData.launchalert -local text="" -if playerData.launchalert==true then -text=string.format("%s, missile launch alerts are now ENABLED.",playerData.name) -else -text=string.format("%s, missile launch alerts are now DISABLED.",playerData.name) -end -MESSAGE:New(text,5):ToClient(playerData.client) -end -end -end -function FOX:_ToggleLaunchMark(_unitname) -self:F2(_unitname) -local unit,playername=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local playerData=self.players[playername] -if playerData then -playerData.marklaunch=not playerData.marklaunch -local text="" -if playerData.marklaunch==true then -text=string.format("%s, missile launch marks are now ENABLED.",playerData.name) -else -text=string.format("%s, missile launch marks are now DISABLED.",playerData.name) -end -MESSAGE:New(text,5):ToClient(playerData.client) -end -end -end -function FOX:_ToggleDestroyMissiles(_unitname) -self:F2(_unitname) -local unit,playername=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local playerData=self.players[playername] -if playerData then -playerData.destroy=not playerData.destroy -local text="" -if playerData.destroy==true then -text=string.format("%s, incoming missiles will be DESTROYED.",playerData.name) -else -text=string.format("%s, incoming missiles will NOT be DESTROYED.",playerData.name) -end -MESSAGE:New(text,5):ToClient(playerData.client) -end -end -end -function FOX:_DeadText() -local texts={} -texts[1]="You're dead!" -texts[2]="Meet your maker!" -texts[3]="Time to meet your maker!" -texts[4]="Well, I guess that was it!" -texts[5]="Bye, bye!" -texts[6]="Cheers buddy, was nice knowing you!" -local r=math.random(#texts) -return texts[r] -end -function FOX:_CheckCoordSafe(coord) -if#self.safezones==0 then -return true -end -for _,_zone in pairs(self.safezones)do -local zone=_zone -local Vec2={x=coord.x,y=coord.z} -local inzone=zone:IsVec2InZone(Vec2) -if inzone then -return true -end -end -return false -end -function FOX:_CheckCoordLaunch(coord) -if#self.launchzones==0 then -return true -end -for _,_zone in pairs(self.launchzones)do -local zone=_zone -local Vec2={x=coord.x,y=coord.z} -local inzone=zone:IsVec2InZone(Vec2) -if inzone then -return true -end -end -return false -end -function FOX:_GetWeapongHeading(weapon) -if weapon and weapon:isExist()then -local wp=weapon:getPosition() -local wph=math.atan2(wp.x.z,wp.x.x) -if wph<0 then -wph=wph+2*math.pi -end -wph=math.deg(wph) -return wph -end -return-1 -end -function FOX:_SayNotchingHeadings(playerData,weapon) -if playerData and playerData.unit and playerData.unit:IsAlive()then -local nr,nl=self:_GetNotchingHeadings(weapon) -if nr and nl then -local text=string.format("Notching heading %03d° or %03d°",nr,nl) -MESSAGE:New(text,5,"FOX"):ToClient(playerData.client) -end -end -end -function FOX:_GetNotchingHeadings(weapon) -if weapon then -local hdg=self:_GetWeapongHeading(weapon) -local hdg1=hdg+90 -if hdg1>360 then -hdg1=hdg1-360 -end -local hdg2=hdg-90 -if hdg2<0 then -hdg2=hdg2+360 -end -return hdg1,hdg2 -end -return nil,nil -end -function FOX:_GetPlayerFromUnitname(unitName) -for _,_player in pairs(self.players)do -local player=_player -if player.unitname==unitName then -return player -end -end -return nil -end -function FOX:_GetPlayerFromUnit(unit) -if unit and unit:IsAlive()then -local unitname=unit:GetName() -for _,_player in pairs(self.players)do -local player=_player -if player.unitname==unitname then -return player -end -end -end -return nil -end -function FOX:_GetPlayerUnitAndName(_unitName) -self:F2(_unitName) -if _unitName~=nil then -local DCSunit=Unit.getByName(_unitName) -if DCSunit then -local playername=DCSunit:getPlayerName() -local unit=UNIT:Find(DCSunit) -self:T2({DCSunit=DCSunit,unit=unit,playername=playername}) -if DCSunit and unit and playername then -self:T(self.lid..string.format("Found DCS unit %s with player %s.",tostring(_unitName),tostring(playername))) -return unit,playername -end -end -end -return nil,nil -end -MANTIS={ -ClassName="MANTIS", -name="mymantis", -SAM_Templates_Prefix="", -SAM_Group=nil, -EWR_Templates_Prefix="", -EWR_Group=nil, -Adv_EWR_Group=nil, -HQ_Template_CC="", -HQ_CC=nil, -SAM_Table={}, -SAM_Table_Long={}, -SAM_Table_Medium={}, -SAM_Table_Short={}, -lid="", -Detection=nil, -AWACS_Detection=nil, -debug=false, -checkradius=25000, -grouping=5000, -acceptrange=80000, -detectinterval=30, -engagerange=95, -autorelocate=false, -advanced=false, -adv_ratio=100, -adv_state=0, -AWACS_Prefix="", -advAwacs=false, -verbose=false, -awacsrange=250000, -Shorad=nil, -ShoradLink=false, -ShoradTime=600, -ShoradActDistance=25000, -UseEmOnOff=false, -TimeStamp=0, -state2flag=false, -SamStateTracker={}, -DLink=false, -DLTimeStamp=0, -Padding=10, -SuppressedGroups={}, -automode=true, -autoshorad=true, -ShoradGroupSet=nil, -} -MANTIS.AdvancedState={ -GREEN=0, -AMBER=1, -RED=2, -} -MANTIS.SamType={ -SHORT="Short", -MEDIUM="Medium", -LONG="Long", -} -MANTIS.SamData={ -["Hawk"]={Range=35,Blindspot=0,Height=12,Type="Medium",Radar="Hawk"}, -["NASAMS"]={Range=14,Blindspot=0,Height=7,Type="Short",Radar="NSAMS"}, -["Patriot"]={Range=99,Blindspot=0,Height=25,Type="Long",Radar="Patriot"}, -["Rapier"]={Range=10,Blindspot=0,Height=3,Type="Short",Radar="rapier"}, -["SA-2"]={Range=40,Blindspot=7,Height=25,Type="Medium",Radar="S_75M_Volhov"}, -["SA-3"]={Range=18,Blindspot=6,Height=18,Type="Short",Radar="5p73 s-125 ln"}, -["SA-5"]={Range=250,Blindspot=7,Height=40,Type="Long",Radar="5N62V"}, -["SA-6"]={Range=25,Blindspot=0,Height=8,Type="Medium",Radar="1S91"}, -["SA-10"]={Range=119,Blindspot=0,Height=18,Type="Long",Radar="S-300PS 4"}, -["SA-11"]={Range=35,Blindspot=0,Height=20,Type="Medium",Radar="SA-11"}, -["Roland"]={Range=5,Blindspot=0,Height=5,Type="Short",Radar="Roland"}, -["HQ-7"]={Range=12,Blindspot=0,Height=3,Type="Short",Radar="HQ-7"}, -["SA-9"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="Strela"}, -["SA-8"]={Range=10,Blindspot=0,Height=5,Type="Short",Radar="Osa 9A33"}, -["SA-19"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Tunguska"}, -["SA-15"]={Range=11,Blindspot=0,Height=6,Type="Short",Radar="Tor 9A331"}, -["SA-13"]={Range=5,Blindspot=0,Height=3,Type="Short",Radar="Strela"}, -["Avenger"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="Avenger"}, -["Chaparral"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Chaparral"}, -["Linebacker"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="Linebacker"}, -["Silkworm"]={Range=90,Blindspot=1,Height=0.2,Type="Long",Radar="Silkworm"}, -["SA-10B"]={Range=75,Blindspot=0,Height=18,Type="Medium",Radar="SA-10B"}, -["SA-17"]={Range=50,Blindspot=3,Height=30,Type="Medium",Radar="SA-17"}, -["SA-20A"]={Range=150,Blindspot=5,Height=27,Type="Long",Radar="S-300PMU1"}, -["SA-20B"]={Range=200,Blindspot=4,Height=27,Type="Long",Radar="S-300PMU2"}, -["HQ-2"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"}, -["SHORAD"]={Range=3,Blindspot=0,Height=3,Type="Short",Radar="Igla"}, -["TAMIR IDFA"]={Range=20,Blindspot=0.6,Height=12.3,Type="Short",Radar="IRON_DOME_LN"}, -["STUNNER IDFA"]={Range=250,Blindspot=1,Height=45,Type="Long",Radar="DAVID_SLING_LN"}, -} -MANTIS.SamDataHDS={ -["SA-2 HDS"]={Range=56,Blindspot=7,Height=30,Type="Medium",Radar="V759"}, -["SA-3 HDS"]={Range=20,Blindspot=6,Height=30,Type="Short",Radar="V-601P"}, -["SA-10C HDS 2"]={Range=90,Blindspot=5,Height=25,Type="Long",Radar="5P85DE ln"}, -["SA-10C HDS 1"]={Range=90,Blindspot=5,Height=25,Type="Long",Radar="5P85CE ln"}, -["SA-12 HDS 2"]={Range=100,Blindspot=10,Height=25,Type="Long",Radar="S-300V 9A82 l"}, -["SA-12 HDS 1"]={Range=75,Blindspot=1,Height=25,Type="Long",Radar="S-300V 9A83 l"}, -["SA-23 HDS 2"]={Range=200,Blindspot=5,Height=37,Type="Long",Radar="S-300VM 9A82ME"}, -["SA-23 HDS 1"]={Range=100,Blindspot=1,Height=50,Type="Long",Radar="S-300VM 9A83ME"}, -["HQ-2 HDS"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"}, -} -MANTIS.SamDataSMA={ -["RBS98M SMA"]={Range=20,Blindspot=0,Height=8,Type="Short",Radar="RBS-98"}, -["RBS70 SMA"]={Range=8,Blindspot=0,Height=5.5,Type="Short",Radar="RBS-70"}, -["RBS70M SMA"]={Range=8,Blindspot=0,Height=5.5,Type="Short",Radar="BV410_RBS70"}, -["RBS90 SMA"]={Range=8,Blindspot=0,Height=5.5,Type="Short",Radar="RBS-90"}, -["RBS90M SMA"]={Range=8,Blindspot=0,Height=5.5,Type="Short",Radar="BV410_RBS90"}, -["RBS103A SMA"]={Range=150,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_Rb103A"}, -["RBS103B SMA"]={Range=35,Blindspot=0,Height=36,Type="Medium",Radar="LvS-103_Lavett103_Rb103B"}, -["RBS103AM SMA"]={Range=150,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103A"}, -["RBS103BM SMA"]={Range=35,Blindspot=0,Height=36,Type="Medium",Radar="LvS-103_Lavett103_HX_Rb103B"}, -["Lvkv9040M SMA"]={Range=4,Blindspot=0,Height=2.5,Type="Short",Radar="LvKv9040"}, -} -MANTIS.SamDataCH={ -["2S38 CH"]={Range=8,Blindspot=0.5,Height=6,Type="Short",Radar="2S38"}, -["PantsirS1 CH"]={Range=20,Blindspot=1.2,Height=15,Type="Short",Radar="PantsirS1"}, -["PantsirS2 CH"]={Range=30,Blindspot=1.2,Height=18,Type="Medium",Radar="PantsirS2"}, -["PGL-625 CH"]={Range=10,Blindspot=0.5,Height=5,Type="Short",Radar="PGL_625"}, -["HQ-17A CH"]={Range=20,Blindspot=1.5,Height=10,Type="Short",Radar="HQ17A"}, -["M903PAC2 CH"]={Range=160,Blindspot=3,Height=24.5,Type="Long",Radar="MIM104_M903_PAC2"}, -["M903PAC3 CH"]={Range=120,Blindspot=1,Height=40,Type="Long",Radar="MIM104_M903_PAC3"}, -["TorM2 CH"]={Range=12,Blindspot=1,Height=10,Type="Short",Radar="TorM2"}, -["TorM2K CH"]={Range=12,Blindspot=1,Height=10,Type="Short",Radar="TorM2K"}, -["TorM2M CH"]={Range=16,Blindspot=1,Height=10,Type="Short",Radar="TorM2M"}, -["NASAMS3-AMRAAMER CH"]={Range=50,Blindspot=2,Height=35.7,Type="Medium",Radar="CH_NASAMS3_LN_AMRAAM_ER"}, -["NASAMS3-AIM9X2 CH"]={Range=20,Blindspot=0.2,Height=18,Type="Short",Radar="CH_NASAMS3_LN_AIM9X2"}, -["C-RAM CH"]={Range=2,Blindspot=0,Height=2,Type="Short",Radar="CH_Centurion_C_RAM"}, -["PGZ-09 CH"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="CH_PGZ09"}, -["S350-9M100 CH"]={Range=15,Blindspot=1.5,Height=8,Type="Short",Radar="CH_S350_50P6_9M100"}, -["S350-9M96D CH"]={Range=150,Blindspot=2.5,Height=30,Type="Long",Radar="CH_S350_50P6_9M96D"}, -["LAV-AD CH"]={Range=8,Blindspot=0.2,Height=4.8,Type="Short",Radar="CH_LAVAD"}, -["HQ-22 CH"]={Range=170,Blindspot=5,Height=27,Type="Long",Radar="CH_HQ22_LN"}, -} -do -function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs,EmOnOff,Padding,Zones) -local self=BASE:Inherit(self,FSM:New()) -self.SAM_Templates_Prefix=samprefix or"Red SAM" -self.EWR_Templates_Prefix=ewrprefix or"Red EWR" -self.HQ_Template_CC=hq or nil -self.Coalition=coalition or"red" -self.SAM_Table={} -self.SAM_Table_Long={} -self.SAM_Table_Medium={} -self.SAM_Table_Short={} -self.dynamic=dynamic or false -self.checkradius=25000 -self.grouping=5000 -self.acceptrange=80000 -self.detectinterval=30 -self.engagerange=95 -self.autorelocate=false -self.autorelocateunits={HQ=false,EWR=false} -self.advanced=false -self.adv_ratio=100 -self.adv_state=0 -self.verbose=false -self.Adv_EWR_Group=nil -self.AWACS_Prefix=awacs or nil -self.awacsrange=250000 -self.Shorad=nil -self.ShoradLink=false -self.ShoradTime=600 -self.ShoradActDistance=25000 -self.TimeStamp=timer.getAbsTime() -self.relointerval=math.random(1800,3600) -self.state2flag=false -self.SamStateTracker={} -self.DLink=false -self.Padding=Padding or 10 -self.SuppressedGroups={} -self.automode=true -self.radiusscale={} -self.radiusscale[MANTIS.SamType.LONG]=1.1 -self.radiusscale[MANTIS.SamType.MEDIUM]=1.2 -self.radiusscale[MANTIS.SamType.SHORT]=1.3 -self.usezones=false -self.AcceptZones={} -self.RejectZones={} -self.ConflictZones={} -self.maxlongrange=1 -self.maxmidrange=2 -self.maxshortrange=2 -self.maxclassic=6 -self.autoshorad=true -self.ShoradGroupSet=SET_GROUP:New() -self.FilterZones=Zones -self.SkateZones=nil -self.SkateNumber=3 -self.shootandscoot=false -self.UseEmOnOff=true -if EmOnOff==false then -self.UseEmOnOff=false -end -if type(awacs)=="string"then -self.advAwacs=true -else -self.advAwacs=false -end -self.lid=string.format("MANTIS %s | ",self.name) -if self.debug then -BASE:TraceOnOff(true) -BASE:TraceClass(self.ClassName) -BASE:TraceLevel(1) -end -self.ewr_templates={} -if type(samprefix)~="table"then -self.SAM_Templates_Prefix={samprefix} -end -if type(ewrprefix)~="table"then -self.EWR_Templates_Prefix={ewrprefix} -end -for _,_group in pairs(self.SAM_Templates_Prefix)do -table.insert(self.ewr_templates,_group) -end -for _,_group in pairs(self.EWR_Templates_Prefix)do -table.insert(self.ewr_templates,_group) -end -if self.advAwacs then -table.insert(self.ewr_templates,awacs) -end -self:T({self.ewr_templates}) -self.SAM_Group=SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition) -self.EWR_Group=SET_GROUP:New():FilterPrefixes(self.ewr_templates):FilterCoalitions(self.Coalition) -if self.FilterZones then -self.SAM_Group:FilterZones(self.FilterZones) -end -if self.dynamic then -self.SAM_Group:FilterStart() -self.EWR_Group:FilterStart() -else -self.SAM_Group:FilterOnce() -self.EWR_Group:FilterOnce() -end -if self.HQ_Template_CC then -self.HQ_CC=GROUP:FindByName(self.HQ_Template_CC) -end -self.version="0.8.16" -self:I(string.format("***** Starting MANTIS Version %s *****",self.version)) -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","Status","*") -self:AddTransition("*","Relocating","*") -self:AddTransition("*","GreenState","*") -self:AddTransition("*","RedState","*") -self:AddTransition("*","AdvStateChange","*") -self:AddTransition("*","ShoradActivated","*") -self:AddTransition("*","SeadSuppressionStart","*") -self:AddTransition("*","SeadSuppressionEnd","*") -self:AddTransition("*","SeadSuppressionPlanned","*") -self:AddTransition("*","Stop","Stopped") -return self -end -function MANTIS:_GetSAMTable() -self:T(self.lid.."GetSAMTable") -return self.SAM_Table -end -function MANTIS:_SetSAMTable(table) -self:T(self.lid.."SetSAMTable") -self.SAM_Table=table -return self -end -function MANTIS:SetEWRGrouping(radius) -self:T(self.lid.."SetEWRGrouping") -local radius=radius or 5000 -self.grouping=radius -return self -end -function MANTIS:AddScootZones(ZoneSet,Number,Random,Formation) -self:T(self.lid.." AddScootZones") -self.SkateZones=ZoneSet -self.SkateNumber=Number or 3 -self.shootandscoot=true -self.ScootRandom=Random -self.ScootFormation=Formation or"Cone" -return self -end -function MANTIS:AddZones(AcceptZones,RejectZones,ConflictZones) -self:T(self.lid.."AddZones") -self.AcceptZones=AcceptZones or{} -self.RejectZones=RejectZones or{} -self.ConflictZones=ConflictZones or{} -if#AcceptZones>0 or#RejectZones>0 or#ConflictZones>0 then -self.usezones=true -end -return self -end -function MANTIS:SetEWRRange(radius) -self:T(self.lid.."SetEWRRange") -return self -end -function MANTIS:SetSAMRadius(radius) -self:T(self.lid.."SetSAMRadius") -local radius=radius or 25000 -self.checkradius=radius -return self -end -function MANTIS:SetSAMRange(range) -self:T(self.lid.."SetSAMRange") -local range=range or 95 -if range<0 or range>100 then -range=95 -end -self.engagerange=range -return self -end -function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic) -self:T(self.lid.."SetMaxActiveSAMs") -self.maxclassic=Classic or 6 -self.maxlongrange=Long or 1 -self.maxmidrange=Mid or 2 -self.maxshortrange=Short or 2 -return self -end -function MANTIS:SetNewSAMRangeWhileRunning(range) -self:T(self.lid.."SetNewSAMRangeWhileRunning") -local range=range or 95 -if range<0 or range>100 then -range=95 -end -self.engagerange=range -self:_RefreshSAMTable() -self.mysead.EngagementRange=range -return self -end -function MANTIS:Debug(onoff) -self:T(self.lid.."SetDebug") -local onoff=onoff or false -self.debug=onoff -if onoff then -BASE:TraceOn() -BASE:TraceClass("MANTIS") -BASE:TraceLevel(1) -else -BASE:TraceOff() -end -return self -end -function MANTIS:GetCommandCenter() -self:T(self.lid.."GetCommandCenter") -if self.HQ_CC then -return self.HQ_CC -else -return nil -end -end -function MANTIS:SetAwacs(prefix) -self:T(self.lid.."SetAwacs") -if prefix~=nil then -if type(prefix)=="string"then -self.AWACS_Prefix=prefix -self.advAwacs=true -end -end -return self -end -function MANTIS:SetAwacsRange(range) -self:T(self.lid.."SetAwacsRange") -local range=range or 250000 -self.awacsrange=range -return self -end -function MANTIS:SetCommandCenter(group) -self:T(self.lid.."SetCommandCenter") -local group=group or nil -if group~=nil then -if type(group)=="string"then -self.HQ_CC=GROUP:FindByName(group) -self.HQ_Template_CC=group -else -self.HQ_CC=group -self.HQ_Template_CC=group:GetName() -end -end -return self -end -function MANTIS:SetDetectInterval(interval) -self:T(self.lid.."SetDetectInterval") -local interval=interval or 30 -self.detectinterval=interval -return self -end -function MANTIS:SetAdvancedMode(onoff,ratio) -self:T(self.lid.."SetAdvancedMode") -local onoff=onoff or false -local ratio=ratio or 100 -if(type(self.HQ_Template_CC)=="string")and onoff and self.dynamic then -self.adv_ratio=ratio -self.advanced=true -self.adv_state=0 -self.Adv_EWR_Group=SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart() -self:I(string.format("***** Starting Advanced Mode MANTIS Version %s *****",self.version)) -else -local text=self.lid.." Advanced Mode requires a HQ and dynamic to be set. Revisit your MANTIS:New() statement to add both." -local m=MESSAGE:New(text,10,"MANTIS",true):ToAll() -self:E(text) -end -return self -end -function MANTIS:SetUsingEmOnOff(switch) -self:T(self.lid.."SetUsingEmOnOff") -self.UseEmOnOff=switch or false -return self -end -function MANTIS:SetUsingDLink(DLink) -self:T(self.lid.."SetUsingDLink") -self.DLink=true -self.Detection=DLink -self.DLTimeStamp=timer.getAbsTime() -return self -end -function MANTIS:_CheckHQState() -self:T(self.lid.."CheckHQState") -local text=self.lid.." Checking HQ State" -local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) -if self.verbose then self:I(text)end -if self.advanced then -local hq=self.HQ_Template_CC -local hqgrp=GROUP:FindByName(hq) -if hqgrp then -if hqgrp:IsAlive()then -return true -else -return false -end -end -end -return self -end -function MANTIS:_CheckEWRState() -self:T(self.lid.."CheckEWRState") -local text=self.lid.." Checking EWR State" -local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) -if self.verbose then self:I(text)end -if self.advanced then -local EWR_Group=self.Adv_EWR_Group -local nalive=EWR_Group:CountAlive() -if self.advAwacs then -local awacs=GROUP:FindByName(self.AWACS_Prefix) -if awacs~=nil then -if awacs:IsAlive()then -nalive=nalive+1 -end -end -end -if nalive>0 then -return true -else -return false -end -end -return self -end -function MANTIS:_CalcAdvState() -self:T(self.lid.."CalcAdvState") -local m=MESSAGE:New(self.lid.." Calculating Advanced State",10,"MANTIS"):ToAllIf(self.debug) -if self.verbose then self:I(self.lid.." Calculating Advanced State")end -local currstate=self.adv_state -local EWR_State=self:_CheckEWRState() -local HQ_State=self:_CheckHQState() -if EWR_State and HQ_State then -self.adv_state=0 -elseif EWR_State or HQ_State then -self.adv_state=1 -else -self.adv_state=2 -end -local interval=self.detectinterval -local ratio=self.adv_ratio/100 -ratio=ratio*self.adv_state -local newinterval=interval+(interval*ratio) -if self.debug or self.verbose then -local text=self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d",currstate,self.adv_state,newinterval) -local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) -if self.verbose then self:I(text)end -end -return newinterval,currstate -end -function MANTIS:SetAutoRelocate(hq,ewr) -self:T(self.lid.."SetAutoRelocate") -local hqrel=hq or false -local ewrel=ewr or false -if hqrel or ewrel then -self.autorelocate=true -self.autorelocateunits={HQ=hqrel,EWR=ewrel} -end -return self -end -function MANTIS:_RelocateGroups() -self:T(self.lid.."RelocateGroups") -local text=self.lid.." Relocating Groups" -local m=MESSAGE:New(text,10,"MANTIS",true):ToAllIf(self.debug) -if self.verbose then self:I(text)end -if self.autorelocate then -local HQGroup=self.HQ_CC -if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive()then -local _hqgrp=self.HQ_CC -local text=self.lid.." Relocating HQ" -_hqgrp:RelocateGroundRandomInRadius(20,500,true,true,nil,true) -end -if self.autorelocateunits.EWR then -local EWR_GRP=SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce() -local EWR_Grps=EWR_GRP.Set -for _,_grp in pairs(EWR_Grps)do -if _grp:IsAlive()and _grp:IsGround()then -local text=self.lid.." Relocating EWR ".._grp:GetName() -local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) -if self.verbose then self:I(text)end -_grp:RelocateGroundRandomInRadius(20,500,true,true,nil,true) -end -end -end -end -return self -end -function MANTIS:_CheckCoordinateInZones(coord) -self:T(self.lid.."_CheckCoordinateInZones") -local inzone=false -if#self.AcceptZones>0 then -for _,_zone in pairs(self.AcceptZones)do -local zone=_zone -if zone:IsCoordinateInZone(coord)then -inzone=true -self:T(self.lid.."Target coord in Accept Zone!") -break -end -end -end -if#self.RejectZones>0 and inzone then -for _,_zone in pairs(self.RejectZones)do -local zone=_zone -if zone:IsCoordinateInZone(coord)then -inzone=false -self:T(self.lid.."Target coord in Reject Zone!") -break -end -end -end -if#self.ConflictZones>0 and not inzone then -for _,_zone in pairs(self.ConflictZones)do -local zone=_zone -if zone:IsCoordinateInZone(coord)then -inzone=true -self:T(self.lid.."Target coord in Conflict Zone!") -break -end -end -end -return inzone -end -function MANTIS:_PreFilterHeight(height) -self:T(self.lid.."_PreFilterHeight") -local set={} -local dlink=self.Detection -local detectedgroups=dlink:GetContactTable() -for _,_contact in pairs(detectedgroups)do -local contact=_contact -local grp=contact.group -if grp:IsAlive()then -if grp:GetHeight(true)65 then -self:_RefreshSAMTable() -end -if self.automode then -local samset=self.SAM_Table_Long -self:_CheckLoop(samset,detset,dlink,self.maxlongrange) -local samset=self.SAM_Table_Medium -self:_CheckLoop(samset,detset,dlink,self.maxmidrange) -local samset=self.SAM_Table_Short -self:_CheckLoop(samset,detset,dlink,self.maxshortrange) -else -local samset=self:_GetSAMTable() -self:_CheckLoop(samset,detset,dlink,self.maxclassic) -end -return self -end -function MANTIS:_Relocate() -self:T(self.lid.."Relocate") -self:_RelocateGroups() -return self -end -function MANTIS:_CheckAdvState() -self:T(self.lid.."CheckAdvSate") -local interval,oldstate=self:_CalcAdvState() -local newstate=self.adv_state -if newstate~=oldstate then -self:__AdvStateChange(1,oldstate,newstate,interval) -if newstate==2 then -self.state2flag=true -local samset=self:_GetSAMTable() -for _,_data in pairs(samset)do -local name=_data[1] -local samgroup=GROUP:FindByName(name) -if samgroup:IsAlive()then -if self.UseEmOnOff then -samgroup:EnableEmission(true) -else -samgroup:OptionAlarmStateRed() -end -end -end -elseif newstate<=1 then -self.detectinterval=interval -self.state2flag=false -end -end -return self -end -function MANTIS:_CheckDLinkState() -self:T(self.lid.."_CheckDLinkState") -local dlink=self.Detection -local TS=timer.getAbsTime() -if not dlink:Is("Running")and(TS-self.DLTimeStamp>29)then -self.DLink=false -self.Detection=self:StartDetection() -self:I(self.lid.."Intel DLink not running - switching back to single detection!") -end -end -function MANTIS:onafterStart(From,Event,To) -self:T({From,Event,To}) -self:T(self.lid.."Starting MANTIS") -self:SetSAMStartState() -if not INTEL then -self.Detection=self:StartDetection() -else -self.Detection=self:StartIntelDetection() -end -if self.autoshorad then -self.Shorad=SHORAD:New(self.name.."-SHORAD",self.name.."-SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff) -self.Shorad:SetDefenseLimits(80,95) -self.ShoradLink=true -self.Shorad.Groupset=self.ShoradGroupSet -self.Shorad.debug=self.debug -end -if self.shootandscoot and self.SkateZones and self.Shorad then -self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation) -end -self:__Status(-math.random(1,10)) -return self -end -function MANTIS:onbeforeStatus(From,Event,To) -self:T({From,Event,To}) -if not self.state2flag then -self:_Check(self.Detection,self.DLink) -end -if self.autorelocate then -local relointerval=self.relointerval -local thistime=timer.getAbsTime() -local timepassed=thistime-self.TimeStamp -local halfintv=math.floor(timepassed/relointerval) -if halfintv>=1 then -self.TimeStamp=timer.getAbsTime() -self:_Relocate() -self:__Relocating(1) -end -end -if self.advanced then -self:_CheckAdvState() -end -if self.DLink then -self:_CheckDLinkState() -end -return self -end -function MANTIS:onafterStatus(From,Event,To) -self:T({From,Event,To}) -if self.debug and self.verbose then -self:I(self.lid.."Status Report") -for _name,_state in pairs(self.SamStateTracker)do -self:I(string.format("Site %s\tStatus %s",_name,_state)) -end -end -local interval=self.detectinterval*-1 -self:__Status(interval) -return self -end -function MANTIS:onafterStop(From,Event,To) -self:T({From,Event,To}) -return self -end -function MANTIS:onafterRelocating(From,Event,To) -self:T({From,Event,To}) -return self -end -function MANTIS:onafterGreenState(From,Event,To,Group) -self:T({From,Event,To,Group:GetName()}) -return self -end -function MANTIS:onafterRedState(From,Event,To,Group) -self:T({From,Event,To,Group:GetName()}) -return self -end -function MANTIS:onafterAdvStateChange(From,Event,To,Oldstate,Newstate,Interval) -self:T({From,Event,To,Oldstate,Newstate,Interval}) -return self -end -function MANTIS:onafterShoradActivated(From,Event,To,Name,Radius,Ontime) -self:T({From,Event,To,Name,Radius,Ontime}) -return self -end -function MANTIS:onafterSeadSuppressionStart(From,Event,To,Group,Name,Attacker) -self:T({From,Event,To,Name}) -self.SuppressedGroups[Name]=true -if self.ShoradLink then -local Shorad=self.Shorad -local radius=self.checkradius -local ontime=self.ShoradTime -Shorad:WakeUpShorad(Name,radius,ontime) -self:__ShoradActivated(1,Name,radius,ontime) -end -return self -end -function MANTIS:onafterSeadSuppressionEnd(From,Event,To,Group,Name) -self:T({From,Event,To,Name}) -self.SuppressedGroups[Name]=false -return self -end -function MANTIS:onafterSeadSuppressionPlanned(From,Event,To,Group,Name,SuppressionStartTime,SuppressionEndTime,Attacker) -self:T({From,Event,To,Name}) -return self -end -end -SHORAD={ -ClassName="SHORAD", -name="MyShorad", -debug=false, -Prefixes="", -Radius=20000, -Groupset=nil, -Samset=nil, -Coalition=nil, -ActiveTimer=600, -ActiveGroups={}, -lid="", -DefendHarms=true, -DefendMavs=true, -DefenseLowProb=70, -DefenseHighProb=90, -UseEmOnOff=true, -shootandscoot=false, -SkateNumber=3, -SkateZones=nil, -minscootdist=100, -minscootdist=3000, -scootrandomcoord=false, -} -do -SHORAD.Harms={ -["AGM_88"]="AGM_88", -["AGM_122"]="AGM_122", -["AGM_84"]="AGM_84", -["AGM_45"]="AGM_45", -["ALARM"]="ALARM", -["LD-10"]="LD-10", -["X_58"]="X_58", -["X_28"]="X_28", -["X_25"]="X_25", -["X_31"]="X_31", -["Kh25"]="Kh25", -["HY-2"]="HY-2", -["ADM_141A"]="ADM_141A", -} -SHORAD.Mavs={ -["AGM"]="AGM", -["C-701"]="C-701", -["Kh25"]="Kh25", -["Kh29"]="Kh29", -["Kh31"]="Kh31", -["Kh66"]="Kh66", -} -function SHORAD:New(Name,ShoradPrefix,Samset,Radius,ActiveTimer,Coalition,UseEmOnOff) -local self=BASE:Inherit(self,FSM:New()) -self:T({Name,ShoradPrefix,Samset,Radius,ActiveTimer,Coalition}) -local GroupSet=SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart() -self.name=Name or"MyShorad" -self.Prefixes=ShoradPrefix or"SAM SHORAD" -self.Radius=Radius or 20000 -self.Coalition=Coalition or"blue" -self.Samset=Samset or GroupSet -self.ActiveTimer=ActiveTimer or 600 -self.ActiveGroups={} -self.Groupset=GroupSet -self.DefendHarms=true -self.DefendMavs=true -self.DefenseLowProb=70 -self.DefenseHighProb=90 -self.UseEmOnOff=true -if UseEmOnOff==false then self.UseEmOnOff=UseEmOnOff end -self:I("*** SHORAD - Started Version 0.3.4") -self.lid=string.format("SHORAD %s | ",self.name) -self:_InitState() -self:HandleEvent(EVENTS.Shot,self.HandleEventShot) -self:SetStartState("Running") -self:AddTransition("*","WakeUpShorad","*") -self:AddTransition("*","CalculateHitZone","*") -self:AddTransition("*","ShootAndScoot","*") -return self -end -function SHORAD:_InitState() -self:T(self.lid.." _InitState") -local table={} -local set=self.Groupset -self:T({set=set}) -local aliveset=set:GetAliveSet() -for _,_group in pairs(aliveset)do -if self.UseEmOnOff then -_group:EnableEmission(false) -_group:OptionAlarmStateRed() -else -_group:OptionAlarmStateGreen() -end -_group:OptionDisperseOnAttack(30) -end -for i=1,100 do -math.random() -end -return self -end -function SHORAD:AddScootZones(ZoneSet,Number,Random,Formation) -self:T(self.lid.." AddScootZones") -self.SkateZones=ZoneSet -self.SkateNumber=Number or 3 -self.shootandscoot=true -self.scootrandomcoord=Random -self.scootformation=Formation or"Cone" -return self -end -function SHORAD:SwitchDebug(onoff) -self:T({onoff}) -if onoff then -self:SwitchDebugOn() -else -self:SwitchDebugOff() -end -return self -end -function SHORAD:SwitchDebugOn() -self.debug=true -BASE:TraceOn() -BASE:TraceClass("SHORAD") -return self -end -function SHORAD:SwitchDebugOff() -self.debug=false -BASE:TraceOff() -return self -end -function SHORAD:SwitchHARMDefense(onoff) -self:T({onoff}) -local onoff=onoff or true -self.DefendHarms=onoff -return self -end -function SHORAD:SwitchAGMDefense(onoff) -self:T({onoff}) -local onoff=onoff or true -self.DefendMavs=onoff -return self -end -function SHORAD:SetDefenseLimits(low,high) -self:T({low,high}) -local low=low or 70 -local high=high or 90 -if(low<0)or(low>100)or(low>high)then -low=70 -end -if(high<0)or(high>100)or(high%s",location) -end -text=Text..location.."!" -local ttstext=Text..location.."! Repeat! "..location -if _coalition==self.coalition then -if self.verbose then -MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition) -end -if self.SRSRadio then -local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) -sound:SetPlayWithSRS(true) -self.SRS:PlaySoundFile(sound,2) -elseif self.DCSRadio then -self:DCSRadioBroadcast(Soundfile,Soundlength,text) -elseif self.SRSTTSRadio then -if self.SRSPilotVoice then -self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1) -else -self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1) -end -end -end -if _coalition==self.coalition and distancetofarp<=self.maxdistance then -self:T(self.lid.."Spawning new Pilot") -self.pilotindex=self.pilotindex+1 -local newpilot=SPAWN:NewWithAlias(self.template,string.format("%s-AICSAR-%d",self.template,self.pilotindex)) -newpilot:InitDelayOff() -newpilot:OnSpawnGroup( -function(grp) -self.pilotqueue[self.pilotindex]=grp -end -) -newpilot:SpawnFromCoordinate(_LandingPos) -self:__PilotDown(2,_LandingPos,true) -elseif _coalition==self.coalition and distancetofarp>self.maxdistance then -self:T(self.lid.."Pilot out of reach") -self:__PilotDown(2,_LandingPos,false) -end -return self -end -function AICSAR:_EventHandler(EventData,FromEject) -self:T(self.lid.."OnEventLandingAfterEjection ID="..EventData.id) -if self.autoonoff then -if self.playerset:CountAlive()>0 then -return self -end -end -if self.UseEventEject and(not FromEject)then return self end -local _event=EventData -local _LandingPos=COORDINATE:NewFromVec3(_event.initiator:getPosition().p) -local _country=_event.initiator:getCountry() -local _coalition=coalition.getCountryCoalition(_country) -local distancetofarp=_LandingPos:Get2DDistance(self.farp:GetCoordinate()) -local Text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTDOWN",self.locale) -local text="" -local setting={} -setting.MGRS_Accuracy=self.MGRS_Accuracy -local location=_LandingPos:ToStringMGRS(setting) -local msgtxt=Text..location.."!" -location=string.gsub(location,"MGRS ","") -location=string.gsub(location,"%s+","") -location=string.gsub(location,"([%a%d])","%1;") -location=string.gsub(location,"0","zero") -location=string.gsub(location,"9","niner") -location="MGRS;"..location -if self.SRSGoogle then -location=string.format("%s",location) -end -text=Text..location.."!" -local ttstext=Text..location.."! Repeat! "..location -if _coalition==self.coalition then -if self.verbose then -MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition) -end -if self.SRSRadio then -local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) -sound:SetPlayWithSRS(true) -self.SRS:PlaySoundFile(sound,2) -elseif self.DCSRadio then -self:DCSRadioBroadcast(Soundfile,Soundlength,text) -elseif self.SRSTTSRadio then -if self.SRSPilotVoice then -self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1) -else -self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1) -end -end -end -if _coalition==self.coalition and distancetofarp<=self.maxdistance then -self:T(self.lid.."Spawning new Pilot") -self.pilotindex=self.pilotindex+1 -local newpilot=SPAWN:NewWithAlias(self.template,string.format("%s-AICSAR-%d",self.template,self.pilotindex)) -newpilot:InitDelayOff() -newpilot:OnSpawnGroup( -function(grp) -self.pilotqueue[self.pilotindex]=grp -end -) -newpilot:SpawnFromCoordinate(_LandingPos) -Unit.destroy(_event.initiator) -self:__PilotDown(2,_LandingPos,true) -elseif _coalition==self.coalition and distancetofarp>self.maxdistance then -self:T(self.lid.."Pilot out of reach") -self:__PilotDown(2,_LandingPos,false) -end -return self -end -function AICSAR:_GetFlight() -self:T(self.lid.."_GetFlight") -local newhelo=SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.random(1,10000)) -:InitDelayOff() -:InitUnControlled(true) -:OnSpawnGroup( -function(Group) -self:__HeloOnDuty(1,Group) -end -) -:Spawn() -local nhelo=FLIGHTGROUP:New(newhelo) -nhelo:SetHomebase(self.farp) -nhelo:Activate() -return nhelo -end -function AICSAR:_InitMission(Pilot,Index) -self:T(self.lid.."_InitMission") -local pickupzone=ZONE_GROUP:New(Pilot:GetName(),Pilot,self.rescuezoneradius) -local opstransport=OPSTRANSPORT:New(Pilot,pickupzone,self.farpzone) -local helo=self:_GetFlight() -helo.AICSARReserved=true -helo:SetDefaultAltitude(self.Altitude or 1500) -helo:SetDefaultSpeed(self.Speed or 100) -helo:AddOpsTransport(opstransport) -local function AICPickedUp(Helo,Cargo,Index) -self:__PilotPickedUp(2,Helo,Cargo,Index) -end -local function AICHeloDead(Helo,Index) -self:__HeloDown(2,Helo,Index) -end -local function AICHeloUnloaded(Helo,OpsGroup) -self:__PilotUnloaded(2,Helo,OpsGroup) -end -function helo:OnAfterLoading(From,Event,To) -AICPickedUp(helo,helo:GetCargoGroups(),Index) -helo:__LoadingDone(5) -end -function helo:OnAfterDead(From,Event,To) -AICHeloDead(helo,Index) -end -function helo:OnAfterUnloaded(From,Event,To,OpsGroupCargo) -AICHeloUnloaded(helo,OpsGroupCargo) -helo:__UnloadingDone(5) -end -self.helos[Index]=helo -return self -end -function AICSAR:_CheckInMashZone(Pilot) -self:T(self.lid.."_CheckInMashZone") -if Pilot:IsInZone(self.farpzone)then -return true -else -return false -end -end -function AICSAR:SetDefaultSpeed(Knots) -self:T(self.lid.."SetDefaultSpeed") -self.Speed=Knots or 100 -return self -end -function AICSAR:SetDefaultAltitude(Feet) -self:T(self.lid.."SetDefaultAltitude") -self.Altitude=Feet or 1500 -return self -end -function AICSAR:_CheckHelos() -self:T(self.lid.."_CheckHelos") -for _index,_helo in pairs(self.helos)do -local helo=_helo -if helo and helo.ClassName=="FLIGHTGROUP"then -local state=helo:GetState() -local name=helo:GetName() -self:T("Helo group "..name.." in state "..state) -if state=="Arrived"then -helo:__Stop(5) -self.helos[_index]=nil -end -else -self.helos[_index]=nil -end -end -return self -end -function AICSAR:_CountHelos() -self:T(self.lid.."_CountHelos") -local count=0 -for _index,_helo in pairs(self.helos)do -count=count+1 -end -return count -end -function AICSAR:_CheckQueue(OpsGroup) -self:T(self.lid.."_CheckQueue") -for _index,_pilot in pairs(self.pilotqueue)do -local classname=_pilot.ClassName and _pilot.ClassName or"NONE" -local name=_pilot.GroupName and _pilot.GroupName or"NONE" -local playername="John Doe" -local helocount=self:_CountHelos() -if _pilot and _pilot.ClassName and _pilot.ClassName=="GROUP"then -local flightgroup=self.helos[_index] -if self:_CheckInMashZone(_pilot)then -self:T("Pilot".._pilot.GroupName.." rescued!") -if OpsGroup then -OpsGroup:Despawn(10) -else -_pilot:Destroy(true,10) -end -self.pilotqueue[_index]=nil -self.rescued[_index]=true -if self.PilotStore:Count()>0 then -playername=self.PilotStore:Pull() -end -self:__PilotRescued(2,playername) -if flightgroup then -flightgroup.AICSARReserved=false -end -end -if not _pilot.AICSAR then -if self.limithelos and helocount>=self.helonumber then -break -end -_pilot.AICSAR={} -_pilot.AICSAR.Status="Initiated" -_pilot.AICSAR.Boarded=false -self:_InitMission(_pilot,_index) -break -else -if flightgroup then -local state=flightgroup:GetState() -_pilot.AICSAR.Status=state -end -end -end -end -return self -end -function AICSAR:onafterStart(From,Event,To) -self:T({From,Event,To}) -self:__Status(3) -return self -end -function AICSAR:onafterStatus(From,Event,To) -self:T({From,Event,To}) -self:_CheckHelos() -self:__Status(30) -return self -end -function AICSAR:onafterStop(From,Event,To) -self:T({From,Event,To}) -self:UnHandleEvent(EVENTS.LandingAfterEjection) -if self.DCSRadioQueue then -self.DCSRadioQueue:Stop() -end -return self -end -function AICSAR:onafterPilotDown(From,Event,To,Coordinate,InReach) -self:T({From,Event,To}) -local CoordinateText=Coordinate:ToStringMGRS() -local inreach=tostring(InReach) -if InReach then -local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("INITIALOK",self.locale) -self:T(text) -if self.verbose then -MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) -end -if self.SRSRadio then -local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) -sound:SetPlayWithSRS(true) -self.SRS:PlaySoundFile(sound,2) -elseif self.DCSRadio then -self:DCSRadioBroadcast(Soundfile,Soundlength,text) -elseif self.SRSTTSRadio then -if self.SRSOperatorVoice then -self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) -else -self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) -end -end -else -local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("INITIALNOTOK",self.locale) -self:T(text) -if self.verbose then -MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) -end -if self.SRSRadio then -local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) -sound:SetPlayWithSRS(true) -self.SRS:PlaySoundFile(sound,2) -elseif self.DCSRadio then -self:DCSRadioBroadcast(Soundfile,Soundlength,text) -elseif self.SRSTTSRadio then -if self.SRSOperatorVoice then -self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) -else -self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) -end -end -end -self:_CheckQueue() -return self -end -function AICSAR:onafterPilotKIA(From,Event,To) -self:T({From,Event,To}) -local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTKIA",self.locale) -if self.verbose then -MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) -end -if self.SRSRadio then -local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) -sound:SetPlayWithSRS(true) -self.SRS:PlaySoundFile(sound,2) -elseif self.DCSRadio then -self:DCSRadioBroadcast(Soundfile,Soundlength,text) -elseif self.SRSTTSRadio then -self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) -end -return self -end -function AICSAR:onafterHeloDown(From,Event,To,Helo,Index) -self:T({From,Event,To}) -local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("HELODOWN",self.locale) -if self.verbose then -MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) -end -if self.SRSRadio then -local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) -sound:SetPlayWithSRS(true) -self.SRS:PlaySoundFile(sound,2) -elseif self.DCSRadio then -self:DCSRadioBroadcast(Soundfile,Soundlength,text) -elseif self.SRSTTSRadio then -if self.SRSOperatorVoice then -self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) -else -self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) -end -end -local findex=0 -local fhname=Helo:GetName() -if Index and Index>0 then -findex=Index -else -for _index,_helo in pairs(self.helos)do -local helo=_helo -local hname=helo:GetName() -if fhname==hname then -findex=_index -break -end -end -end -if findex>0 and not self.rescued[findex]then -local pilot=self.pilotqueue[findex] -self.helos[findex]=nil -if pilot.AICSAR.Boarded then -self:T("Helo Down: Found DEAD Pilot ID "..findex.." with name "..pilot:GetName()) -self:__PilotKIA(2) -self.pilotqueue[findex]=nil -else -self:T("Helo Down: Found ALIVE Pilot ID "..findex.." with name "..pilot:GetName()) -self:_InitMission(pilot,findex) -end -end -return self -end -function AICSAR:onafterPilotRescued(From,Event,To,PilotName) -self:T({From,Event,To}) -local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTRESCUED",self.locale) -if self.verbose then -MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) -end -if self.SRSRadio then -local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) -sound:SetPlayWithSRS(true) -self.SRS:PlaySoundFile(sound,2) -elseif self.DCSRadio then -self:DCSRadioBroadcast(Soundfile,Soundlength,text) -elseif self.SRSTTSRadio then -self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) -end -return self -end -function AICSAR:onafterPilotUnloaded(From,Event,To,Helo,OpsGroup) -self:T({From,Event,To}) -self:_CheckQueue(OpsGroup) -return self -end -function AICSAR:onafterPilotPickedUp(From,Event,To,Helo,CargoTable,Index) -self:T({From,Event,To}) -local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTINHELO",self.locale) -if self.verbose then -MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) -end -if self.SRSRadio then -local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) -sound:SetPlayWithSRS(true) -self.SRS:PlaySoundFile(sound,2) -elseif self.DCSRadio then -self:DCSRadioBroadcast(Soundfile,Soundlength,text) -elseif self.SRSTTSRadio then -self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) -end -local findex=0 -local fhname=Helo:GetName() -if Index and Index>0 then -findex=Index -else -for _index,_helo in pairs(self.helos)do -local helo=_helo -local hname=helo:GetName() -if fhname==hname then -findex=_index -break -end -end -end -if findex>0 then -local pilot=self.pilotqueue[findex] -self:T("Boarded: Found Pilot ID "..findex.." with name "..pilot:GetName()) -pilot.AICSAR.Boarded=true -end -return self -end -AMMOTRUCK={ -ClassName="AMMOTRUCK", -lid="", -version="0.0.12", -alias="", -debug=false, -trucklist={}, -targetlist={}, -coalition=nil, -truckset=nil, -targetset=nil, -remunitionqueue={}, -waitingtargets={}, -ammothreshold=5, -remunidist=20000, -monitor=-60, -unloadtime=600, -waitingtime=1800, -routeonroad=true, -reloads=5, -} -AMMOTRUCK.State={ -IDLE="idle", -DRIVING="driving", -ARRIVED="arrived", -UNLOADING="unloading", -RETURNING="returning", -WAITING="waiting", -RELOADING="reloading", -OUTOFAMMO="outofammo", -REQUESTED="requested", -} -function AMMOTRUCK:New(Truckset,Targetset,Coalition,Alias,Homezone) -local self=BASE:Inherit(self,FSM:New()) -self.truckset=Truckset -self.targetset=Targetset -self.coalition=Coalition -self.alias=Alias -self.debug=false -self.remunitionqueue={} -self.trucklist={} -self.targetlist={} -self.ammothreshold=5 -self.remunidist=20000 -self.homezone=Homezone -self.waitingtime=1800 -self.usearmygroup=false -self.hasarmygroup=false -self.lid=string.format("AMMOTRUCK %s | %s | ",self.version,self.alias) -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","Monitor","*") -self:AddTransition("*","RouteTruck","*") -self:AddTransition("*","TruckArrived","*") -self:AddTransition("*","TruckUnloading","*") -self:AddTransition("*","TruckReturning","*") -self:AddTransition("*","TruckHome","*") -self:AddTransition("*","Stop","Stopped") -self:__Start(math.random(5,10)) -self:I(self.lid.."Started") -return self -end -function AMMOTRUCK:CheckDrivingTrucks(dataset) -self:T(self.lid.." CheckDrivingTrucks") -local data=dataset -for _,_data in pairs(data)do -local truck=_data -local coord=truck.group:GetCoordinate() -local tgtcoord=truck.targetcoordinate -local dist=coord:Get2DDistance(tgtcoord) -if dist<=150 then -truck.statusquo=AMMOTRUCK.State.ARRIVED -truck.timestamp=timer.getAbsTime() -truck.coordinate=coord -self:__TruckArrived(1,truck) -end -local Tnow=timer.getAbsTime() -if Tnow-truck.timestamp>30 then -local group=truck.group -if self.usearmygroup then -group=truck.group:GetGroup() -end -local currspeed=group:GetVelocityKMH() -if truck.lastspeed then -if truck.lastspeed==0 and currspeed==0 then -self:T(truck.group:GetName().." Is not moving!") -truck.timestamp=timer.getAbsTime() -if self.routeonroad then -group:RouteGroundOnRoad(truck.targetcoordinate,30,2,"Vee") -else -group:RouteGroundTo(truck.targetcoordinate,30,"Vee",2) -end -end -truck.lastspeed=currspeed -else -truck.lastspeed=currspeed -truck.timestamp=timer.getAbsTime() -end -self:I({truck=truck.group:GetName(),currspeed=currspeed,lastspeed=truck.lastspeed}) -end -end -return self -end -function AMMOTRUCK:GetAmmoStatus(Group) -local ammotot,shells,rockets,bombs,missiles,narti=Group:GetAmmunition() -return rockets+missiles+narti -end -function AMMOTRUCK:CheckWaitingTargets(dataset) -self:T(self.lid.." CheckWaitingTargets") -local data=dataset -for _,_data in pairs(data)do -local truck=_data -local Tnow=timer.getAbsTime() -local Tdiff=Tnow-truck.timestamp -if Tdiff>self.waitingtime then -local hasammo=self:GetAmmoStatus(truck.group) -if hasammo<=self.ammothreshold then -truck.statusquo=AMMOTRUCK.State.OUTOFAMMO -else -truck.statusquo=AMMOTRUCK.State.IDLE -end -end -end -return self -end -function AMMOTRUCK:CheckReturningTrucks(dataset) -self:T(self.lid.." CheckReturningTrucks") -local data=dataset -local tgtcoord=self.homezone:GetCoordinate() -local radius=self.homezone:GetRadius() -for _,_data in pairs(data)do -local truck=_data -local coord=truck.group:GetCoordinate() -local dist=coord:Get2DDistance(tgtcoord) -self:T({name=truck.name,radius=radius,distance=dist}) -if dist<=radius then -truck.statusquo=AMMOTRUCK.State.IDLE -truck.timestamp=timer.getAbsTime() -truck.coordinate=coord -truck.reloads=self.reloads or 5 -self:__TruckHome(1,truck) -end -end -return self -end -function AMMOTRUCK:FindTarget(name) -self:T(self.lid.." FindTarget") -local data=nil -local dataset=self.targetlist -for _,_entry in pairs(dataset)do -local entry=_entry -if entry.name==name then -data=entry -break -end -end -return data -end -function AMMOTRUCK:FindTruck(name) -self:T(self.lid.." FindTruck") -local data=nil -local dataset=self.trucklist -for _,_entry in pairs(dataset)do -local entry=_entry -if entry.name==name then -data=entry -break -end -end -return data -end -function AMMOTRUCK:CheckArrivedTrucks(dataset) -self:T(self.lid.." CheckArrivedTrucks") -local data=dataset -for _,_data in pairs(data)do -local truck=_data -truck.statusquo=AMMOTRUCK.State.UNLOADING -truck.timestamp=timer.getAbsTime() -self:__TruckUnloading(2,truck) -local aridata=self:FindTarget(truck.targetname) -if aridata then -aridata.statusquo=AMMOTRUCK.State.RELOADING -aridata.timestamp=timer.getAbsTime() -end -end -return self -end -function AMMOTRUCK:CheckUnloadingTrucks(dataset) -self:T(self.lid.." CheckUnloadingTrucks") -local data=dataset -for _,_data in pairs(data)do -local truck=_data -local Tnow=timer.getAbsTime() -local Tpassed=Tnow-truck.timestamp -local hasammo=self:GetAmmoStatus(truck.targetgroup) -if Tpassed>self.unloadtime and hasammo>self.ammothreshold then -truck.statusquo=AMMOTRUCK.State.RETURNING -truck.timestamp=timer.getAbsTime() -self:__TruckReturning(2,truck) -local aridata=self:FindTarget(truck.targetname) -if aridata then -aridata.statusquo=AMMOTRUCK.State.IDLE -aridata.timestamp=timer.getAbsTime() -end -end -end -return self -end -function AMMOTRUCK:CheckTargetsAlive() -self:T(self.lid.." CheckTargetsAlive") -local arilist=self.targetlist -for _,_ari in pairs(arilist)do -local ari=_ari -if ari.group and ari.group:IsAlive()then -else -self.targetlist[ari.name]=nil -end -end -local aritable=self.targetset:GetSetObjects() -for _,_ari in pairs(aritable)do -local ari=_ari -if ari and ari:IsAlive()and not self.targetlist[ari:GetName()]then -local name=ari:GetName() -local newari={} -newari.name=name -newari.group=ari -newari.statusquo=AMMOTRUCK.State.IDLE -newari.timestamp=timer.getAbsTime() -newari.coordinate=ari:GetCoordinate() -local hasammo=self:GetAmmoStatus(ari) -newari.ammo=hasammo -self.targetlist[name]=newari -end -end -return self -end -function AMMOTRUCK:CheckTrucksAlive() -self:T(self.lid.." CheckTrucksAlive") -local trucklist=self.trucklist -for _,_truck in pairs(trucklist)do -local truck=_truck -if truck.group and truck.group:IsAlive()then -else -local tgtname=truck.targetname -local targetdata=self:FindTarget(tgtname) -if targetdata then -if targetdata.statusquo~=AMMOTRUCK.State.IDLE then -targetdata.statusquo=AMMOTRUCK.State.IDLE -end -end -self.trucklist[truck.name]=nil -end -end -local trucktable=self.truckset:GetSetObjects() -for _,_truck in pairs(trucktable)do -local truck=_truck -if truck and truck:IsAlive()and not self.trucklist[truck:GetName()]then -local name=truck:GetName() -local newtruck={} -newtruck.name=name -newtruck.group=truck -if self.hasarmygroup then -if truck.ClassName and truck.ClassName=="GROUP"then -local trucker=ARMYGROUP:New(truck) -trucker:Activate() -newtruck.group=trucker -end -end -newtruck.statusquo=AMMOTRUCK.State.IDLE -newtruck.timestamp=timer.getAbsTime() -newtruck.coordinate=truck:GetCoordinate() -newtruck.reloads=self.reloads or 5 -self.trucklist[name]=newtruck -end -end -return self -end -function AMMOTRUCK:onafterStart(From,Event,To) -self:T({From,Event,To}) -if ARMYGROUP and self.usearmygroup then -self.hasarmygroup=true -else -self.hasarmygroup=false -end -if self.debug then -BASE:TraceOn() -BASE:TraceClass("AMMOTRUCK") -end -self:CheckTargetsAlive() -self:CheckTrucksAlive() -self:__Monitor(-30) -return self -end -function AMMOTRUCK:onafterMonitor(From,Event,To) -self:T({From,Event,To}) -self:CheckTargetsAlive() -self:CheckTrucksAlive() -local remunition=false -local remunitionqueue={} -local waitingtargets={} -for _,_ari in pairs(self.targetlist)do -local data=_ari -if data.group and data.group:IsAlive()then -data.ammo=self:GetAmmoStatus(data.group) -data.timestamp=timer.getAbsTime() -local text=string.format("Ari %s | Ammo %d | State %s",data.name,data.ammo,data.statusquo) -self:T(text) -if data.ammo<=self.ammothreshold and(data.statusquo==AMMOTRUCK.State.IDLE or data.statusquo==AMMOTRUCK.State.OUTOFAMMO)then -data.statusquo=AMMOTRUCK.State.OUTOFAMMO -remunitionqueue[#remunitionqueue+1]=data -remunition=true -elseif data.statusquo==AMMOTRUCK.State.WAITING then -waitingtargets[#waitingtargets+1]=data -end -else -self.targetlist[data.name]=nil -end -end -local idletrucks={} -local drivingtrucks={} -local unloadingtrucks={} -local arrivedtrucks={} -local returningtrucks={} -local found=false -for _,_truckdata in pairs(self.trucklist)do -local data=_truckdata -if data.group and data.group:IsAlive()then -local text=string.format("Truck %s | State %s",data.name,data.statusquo) -self:T(text) -if data.statusquo==AMMOTRUCK.State.IDLE then -idletrucks[#idletrucks+1]=data -found=true -elseif data.statusquo==AMMOTRUCK.State.DRIVING then -drivingtrucks[#drivingtrucks+1]=data -elseif data.statusquo==AMMOTRUCK.State.ARRIVED then -arrivedtrucks[#arrivedtrucks+1]=data -elseif data.statusquo==AMMOTRUCK.State.UNLOADING then -unloadingtrucks[#unloadingtrucks+1]=data -elseif data.statusquo==AMMOTRUCK.State.RETURNING then -returningtrucks[#returningtrucks+1]=data -if data.reloads>0 or data.reloads==-1 then -idletrucks[#idletrucks+1]=data -found=true -end -end -else -self.truckset[data.name]=nil -end -end -local n=0 -if found and remunition then -for _,_truckdata in pairs(idletrucks)do -local truckdata=_truckdata -local truckcoord=truckdata.group:GetCoordinate() -for _,_aridata in pairs(remunitionqueue)do -local aridata=_aridata -local aricoord=aridata.coordinate -local distance=truckcoord:Get2DDistance(aricoord) -if distance<=self.remunidist and aridata.statusquo==AMMOTRUCK.State.OUTOFAMMO and n<=#idletrucks then -n=n+1 -aridata.statusquo=AMMOTRUCK.State.REQUESTED -self:__RouteTruck(n*5,truckdata,aridata) -break -end -end -end -end -if#drivingtrucks>0 then -self:CheckDrivingTrucks(drivingtrucks) -end -if#arrivedtrucks>0 then -self:CheckArrivedTrucks(arrivedtrucks) -end -if#unloadingtrucks>0 then -self:CheckUnloadingTrucks(unloadingtrucks) -end -if#returningtrucks>0 then -self:CheckReturningTrucks(returningtrucks) -end -if#waitingtargets>0 then -self:CheckWaitingTargets(waitingtargets) -end -self:__Monitor(self.monitor) -return self -end -function AMMOTRUCK:onafterRouteTruck(From,Event,To,Truckdata,Aridata) -self:T({From,Event,To,Truckdata.name,Aridata.name}) -local truckdata=Truckdata -local aridata=Aridata -local tgtgrp=aridata.group -local tgtzone=ZONE_GROUP:New(aridata.name,tgtgrp,30) -local tgtcoord=tgtzone:GetRandomCoordinate(15) -if self.hasarmygroup then -local mission=AUFTRAG:NewONGUARD(tgtcoord) -local oldmission=truckdata.group:GetMissionCurrent() -if oldmission then oldmission:Cancel()end -mission:SetTime(5) -mission:SetTeleport(false) -truckdata.group:AddMission(mission) -elseif self.routeonroad then -truckdata.group:RouteGroundOnRoad(tgtcoord,30) -else -truckdata.group:RouteGroundTo(tgtcoord,30) -end -truckdata.statusquo=AMMOTRUCK.State.DRIVING -truckdata.targetgroup=tgtgrp -truckdata.targetname=aridata.name -truckdata.targetcoordinate=tgtcoord -aridata.statusquo=AMMOTRUCK.State.WAITING -aridata.timestamp=timer.getAbsTime() -return self -end -function AMMOTRUCK:onafterTruckUnloading(From,Event,To,Truckdata) -local m=MESSAGE:New("Truck "..Truckdata.name.." unloading!",15,"AmmoTruck"):ToCoalitionIf(self.coalition,self.debug) -local truck=Truckdata -local coord=truck.group:GetCoordinate() -local heading=truck.group:GetHeading() -heading=heading<180 and(360-heading)or(heading-180) -local cid=self.coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA -cid=self.coalition==coalition.side.NEUTRAL and country.id.UN_PEACEKEEPERS or cid -local ammo={} -for i=1,5 do -ammo[i]=SPAWNSTATIC:NewFromType("ammo_cargo","Cargos",cid) -:InitCoordinate(coord:Translate((15+((i-1)*4)),heading)) -:Spawn(0,"AmmoCrate-"..math.random(1,10000)) -end -local function destroyammo(ammo) -for _,_crate in pairs(ammo)do -_crate:Destroy(false) -end -end -local scheduler=SCHEDULER:New(nil,destroyammo,{ammo},self.waitingtime) -if truck.reloads~=-1 then -truck.reloads=truck.reloads-1 -end -return self -end -function AMMOTRUCK:onafterTruckReturning(From,Event,To,Truck) -self:T({From,Event,To,Truck.name}) -local truckdata=Truck -local tgtzone=self.homezone -local tgtcoord=tgtzone:GetRandomCoordinate() -if self.hasarmygroup then -local mission=AUFTRAG:NewONGUARD(tgtcoord) -local oldmission=truckdata.group:GetMissionCurrent() -if oldmission then oldmission:Cancel()end -mission:SetTime(5) -mission:SetTeleport(false) -truckdata.group:AddMission(mission) -elseif self.routeonroad then -truckdata.group:RouteGroundOnRoad(tgtcoord,30,1,"Cone") -else -truckdata.group:RouteGroundTo(tgtcoord,30,"Cone",1) -end -return self -end -function AMMOTRUCK:onafterStop(From,Event,To) -self:T({From,Event,To}) -return self -end -AUTOLASE={ -ClassName="AUTOLASE", -lid="", -verbose=0, -alias="", -debug=false, -smokemenu=true, -} -AUTOLASE.version="0.1.23" -function AUTOLASE:New(RecceSet,Coalition,Alias,PilotSet) -BASE:T({RecceSet,Coalition,Alias,PilotSet}) -local self=BASE:Inherit(self,BASE:New()) -if Coalition and type(Coalition)=="string"then -if Coalition=="blue"then -self.coalition=coalition.side.BLUE -elseif Coalition=="red"then -self.coalition=coalition.side.RED -elseif Coalition=="neutral"then -self.coalition=coalition.side.NEUTRAL -else -self:E("ERROR: Unknown coalition in AUTOLASE!") -end -end -if Alias then -self.alias=tostring(Alias) -else -self.alias="Lion" -if self.coalition then -if self.coalition==coalition.side.RED then -self.alias="Wolf" -elseif self.coalition==coalition.side.BLUE then -self.alias="Fox" -end -end -end -local self=BASE:Inherit(self,INTEL:New(RecceSet,Coalition,Alias)) -self.RecceSet=RecceSet -self.DetectVisual=true -self.DetectOptical=true -self.DetectRadar=true -self.DetectIRST=true -self.DetectRWR=true -self.DetectDLINK=true -self.LaserCodes=UTILS.GenerateLaserCodes() -self.LaseDistance=5000 -self.LaseDuration=300 -self.GroupsByThreat={} -self.UnitsByThreat={} -self.RecceNames={} -self.RecceLaserCode={} -self.RecceSmokeColor={} -self.RecceUnitNames={} -self.maxlasing=4 -self.CurrentLasing={} -self.lasingindex=0 -self.deadunitnotes={} -self.usepilotset=false -self.reporttimeshort=10 -self.reporttimelong=30 -self.smoketargets=false -self.smokecolor=SMOKECOLOR.Red -self.notifypilots=true -self.targetsperrecce={} -self.RecceUnits={} -self.forcecooldown=true -self.cooldowntime=60 -self.useSRS=false -self.SRSPath="" -self.SRSFreq=251 -self.SRSMod=radio.modulation.AM -self.NoMenus=false -self.minthreatlevel=0 -self.blacklistattributes={} -self:SetLaserCodes({1688,1130,4785,6547,1465,4578}) -self.playermenus={} -self.smokemenu=true -self.lid=string.format("AUTOLASE %s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") -self:AddTransition("*","Monitor","*") -self:AddTransition("*","Lasing","*") -self:AddTransition("*","TargetLost","*") -self:AddTransition("*","TargetDestroyed","*") -self:AddTransition("*","RecceKIA","*") -self:AddTransition("*","LaserTimeout","*") -self:AddTransition("*","Cancel","*") -if PilotSet then -self.usepilotset=true -self.pilotset=PilotSet -self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) -end -self:SetClusterAnalysis(false,false) -self:__Start(2) -self:__Monitor(math.random(5,10)) -return self -end -function AUTOLASE:SetLaserCodes(LaserCodes) -self.LaserCodes=(type(LaserCodes)=="table")and LaserCodes or{LaserCodes} -return self -end -function AUTOLASE:SetPilotMenu() -if self.usepilotset then -local pilottable=self.pilotset:GetSetObjects()or{} -for _,_unit in pairs(pilottable)do -local Unit=_unit -if Unit and Unit:IsAlive()then -local Group=Unit:GetGroup() -local unitname=Unit:GetName() -if self.playermenus[unitname]then self.playermenus[unitname]:Remove()end -local lasetopm=MENU_GROUP:New(Group,"Autolase",nil) -self.playermenus[unitname]=lasetopm -local lasemenu=MENU_GROUP_COMMAND:New(Group,"Status",lasetopm,self.ShowStatus,self,Group,Unit) -if self.smokemenu then -local smoke=(self.smoketargets==true)and"off"or"on" -local smoketext=string.format("Switch smoke targets to %s",smoke) -local smokemenu=MENU_GROUP_COMMAND:New(Group,smoketext,lasetopm,self.SetSmokeTargets,self,(not self.smoketargets)) -end -for _,_grp in pairs(self.RecceSet.Set)do -local grp=_grp -local unit=grp:GetUnit(1) -if unit and unit:IsAlive()then -local name=unit:GetName() -local mname=string.gsub(name,".%d+.%d+$","") -local code=self:GetLaserCode(name) -local unittop=MENU_GROUP:New(Group,"Change laser code for "..mname,lasetopm) -for _,_code in pairs(self.LaserCodes)do -local text=tostring(_code) -if _code==code then text=text.."(*)"end -local changemenu=MENU_GROUP_COMMAND:New(Group,text,unittop,self.SetRecceLaserCode,self,name,_code,true) -end -end -end -end -end -else -if not self.NoMenus then -self.Menu=MENU_COALITION_COMMAND:New(self.coalition,"Autolase",nil,self.ShowStatus,self) -end -end -return self -end -function AUTOLASE:_EventHandler(EventData) -self:SetPilotMenu() -return self -end -function AUTOLASE:SetMinThreatLevel(Level) -local level=Level or 0 -if level<0 or level>10 then level=0 end -self.minthreatlevel=level -return self -end -function AUTOLASE:AddBlackListAttributes(Attributes) -local attributes=Attributes -if type(attributes)~="table"then -attributes={attributes} -end -for _,_attr in pairs(attributes)do -table.insert(self.blacklistattributes,_attr) -end -return self -end -function AUTOLASE:GetLaserCode(RecceName) -local code=1688 -if self.RecceLaserCode[RecceName]==nil then -code=self.LaserCodes[math.random(#self.LaserCodes)] -self.RecceLaserCode[RecceName]=code -else -code=self.RecceLaserCode[RecceName] -end -return code -end -function AUTOLASE:GetSmokeColor(RecceName) -local color=self.smokecolor -if self.RecceSmokeColor[RecceName]==nil then -self.RecceSmokeColor[RecceName]=color -else -color=self.RecceSmokeColor[RecceName] -end -return color -end -function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Culture,Port,Voice,Volume,PathToGoogleKey) -if OnOff then -self.useSRS=true -self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone" -self.SRSFreq=Frequency or 271 -self.SRSMod=Modulation or radio.modulation.AM -self.Gender=Gender or MSRS.gender or"male" -self.Culture=Culture or MSRS.culture or"en-US" -self.Port=Port or MSRS.port or 5002 -self.Voice=Voice -self.PathToGoogleKey=PathToGoogleKey -self.Volume=Volume or 1.0 -self.Label=Label -self.SRS=MSRS:New(self.SRSPath,self.SRSFreq,self.SRSMod) -self.SRS:SetCoalition(self.coalition) -self.SRS:SetLabel(self.MenuName or self.Name) -self.SRS:SetGender(self.Gender) -self.SRS:SetCulture(self.Culture) -self.SRS:SetPort(self.Port) -self.SRS:SetVoice(self.Voice) -self.SRS:SetCoalition(self.coalition) -self.SRS:SetVolume(self.Volume) -if self.PathToGoogleKey then -self.SRS:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) -self.SRS:SetProvider(MSRS.Provider.GOOGLE) -end -self.SRSQueue=MSRSQUEUE:New(self.alias) -else -self.useSRS=false -self.SRS=nil -self.SRSQueue=nil -end -return self -end -function AUTOLASE:SetMaxLasingTargets(Number) -self.maxlasing=Number or 4 -return self -end -function AUTOLASE:SetNotifyPilots(OnOff) -self.notifypilots=OnOff and true -return self -end -function AUTOLASE:SetRecceLaserCode(RecceName,Code,Refresh) -local code=Code or 1688 -self.RecceLaserCode[RecceName]=code -if Refresh then -self:SetPilotMenu() -if self.notifypilots then -if string.find(RecceName,"#")then -RecceName=string.match(RecceName,"^(.*)#") -end -self:NotifyPilots(string.format("Code for %s set to: %d",RecceName,Code),15) -end -end -return self -end -function AUTOLASE:SetRecceSmokeColor(RecceName,Color) -local color=Color or self.smokecolor -self.RecceSmokeColor[RecceName]=color -return self -end -function AUTOLASE:SetLaserCoolDown(OnOff,Seconds) -self.forcecooldown=OnOff and true -self.cooldowntime=Seconds or 60 -return self -end -function AUTOLASE:SetReportingTimes(long,short) -self.reporttimeshort=short or 10 -self.reporttimelong=long or 30 -return self -end -function AUTOLASE:SetLasingParameters(Distance,Duration) -self.LaseDistance=Distance or 5000 -self.LaseDuration=Duration or 300 -return self -end -function AUTOLASE:SetSmokeTargets(OnOff,Color) -self.smoketargets=OnOff -self.smokecolor=Color or SMOKECOLOR.Red -local smktxt=OnOff==true and"on"or"off" -local Message="Smoking targets is now "..smktxt.."!" -self:NotifyPilots(Message,10) -return self -end -function AUTOLASE:EnableSmokeMenu() -self.smokemenu=true -return self -end -function AUTOLASE:DisableSmokeMenu() -self.smokemenu=false -return self -end -function AUTOLASE:GetLosFromUnit(Unit) -local lasedistance=self.LaseDistance -local unitheight=Unit:GetHeight() -local coord=Unit:GetCoordinate() -local landheight=coord:GetLandHeight() -local asl=unitheight-landheight -if asl>100 then -local absquare=lasedistance^2+asl^2 -lasedistance=math.sqrt(absquare) -end -return lasedistance -end -function AUTOLASE:CleanCurrentLasing() -local lasingtable=self.CurrentLasing -local newtable={} -local newreccecount={} -local lasing=0 -for _ind,_entry in pairs(lasingtable)do -local entry=_entry -if not newreccecount[entry.reccename]then -newreccecount[entry.reccename]=0 -end -end -for _,_recce in pairs(self.RecceSet:GetSetObjects())do -local recce=_recce -if recce and recce:IsAlive()then -local unit=recce:GetUnit(1) -local name=unit:GetName() -if not self.RecceUnits[name]then -self.RecceUnits[name]={name=name,unit=unit,cooldown=false,timestamp=timer.getAbsTime()} -end -end -end -for _ind,_entry in pairs(lasingtable)do -local entry=_entry -local valid=0 -local reccedead=false -local unitdead=false -local lostsight=false -local timeout=false -local Tnow=timer.getAbsTime() -local recce=entry.lasingunit -if recce and recce:IsAlive()then -valid=valid+1 -else -reccedead=true -self:__RecceKIA(2,entry.reccename) -end -local unit=entry.lasedunit -if unit and unit:IsAlive()==true then -valid=valid+1 -else -unitdead=true -if not self.deadunitnotes[entry.unitname]then -self.deadunitnotes[entry.unitname]=true -self:__TargetDestroyed(2,entry.unitname,entry.reccename) -end -end -if not reccedead and not unitdead then -if self:CanLase(recce,unit)then -valid=valid+1 -else -lostsight=true -entry.laserspot:LaseOff() -self:__TargetLost(2,entry.unitname,entry.reccename) -end -end -local timestamp=entry.timestamp -if Tnow-timestamp=distance and threat>=self.minthreatlevel then -table.insert(groupsbythreat,{contact.group,contact.threatlevel}) -self.RecceNames[contact.groupname]=contact.recce -end -end -end -self.GroupsByThreat=groupsbythreat -if self.verbose>2 and lines>0 then -local m=MESSAGE:New(report:Text(),self.reporttimeshort,"Autolase"):ToAll() -end -table.sort(self.GroupsByThreat,function(a,b) -local aNum=a[2] -local bNum=b[2] -return aNum>bNum -end) -local unitsbythreat={} -for _,_entry in pairs(self.GroupsByThreat)do -local group=_entry[1] -if group and group:IsAlive()then -local units=group:GetUnits() -local reccename=self.RecceNames[group:GetName()] -for _,_unit in pairs(units)do -local unit=_unit -if unit and unit:IsAlive()then -local threat=unit:GetThreatLevel() -local coord=unit:GetCoordinate() -if threat>=self.minthreatlevel then -local unitname=unit:GetName() -if unit:HasAttribute("RADAR_BAND1_FOR_ARM")or unit:HasAttribute("RADAR_BAND2_FOR_ARM")or unit:HasAttribute("Optical Tracker")then -threat=11 -end -table.insert(unitsbythreat,{unit,threat}) -self.RecceUnitNames[unitname]=reccename -end -end -end -end -end -self.UnitsByThreat=unitsbythreat -table.sort(self.UnitsByThreat,function(a,b) -local aNum=a[2] -local bNum=b[2] -return aNum>bNum -end) -local unitreport=REPORT:New("Detected Units") -local lines=0 -for _,_entry in pairs(self.UnitsByThreat)do -local threat=_entry[2] -local unit=_entry[1] -local unitname=unit:GetName() -local text=string.format("Unit %s | Threatlevel %d | Detected by %s",unitname,threat,self.RecceUnitNames[unitname]) -unitreport:Add(text) -lines=lines+1 -self:T(text) -if self.debug then self:I(text)end -end -if self.verbose>2 and lines>0 then -local m=MESSAGE:New(unitreport:Text(),self.reporttimeshort,"Autolase"):ToAll() -end -for _,_detectingunit in pairs(self.RecceUnits)do -local reccename=_detectingunit.name -local recce=_detectingunit.unit -local reccecount=self.targetsperrecce[reccename]or 0 -local targets=0 -for _,_entry in pairs(self.UnitsByThreat)do -local unit=_entry[1] -local unitname=unit:GetName() -local canlase=self:CanLase(recce,unit) -if targets+reccecount0 then -ground:ForEachGroupAlive( -function(grp) -if grp.Tiresias and grp.Tiresias.type and(not grp.Tiresias.exception==true)then -if grp.Tiresias.invisible==true then -grp:SetCommandInvisible(false) -grp.Tiresias.invisible=false -end -if grp.Tiresias.type=="Vehicle"and grp.Tiresias.AIOff and grp.Tiresias.AIOff==true then -grp:SetAIOn() -grp.Tiresias.AIOff=false -end -if SwitchAAA and grp.Tiresias.type=="AAA"and grp.Tiresias.AIOff and grp.Tiresias.AIOff==true then -grp:SetAIOn() -grp:EnableEmission(true) -grp.Tiresias.AIOff=false -end -else -BASE:E("TIRESIAS - This group has not been initialized or is an exception!") -end -end -) -end -return self -end -function TIRESIAS:onafterStart(From,Event,To) -self:T({From,Event,To}) -local VehicleSet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterNotAAA):FilterFunction(TIRESIAS._FilterNotSAM):FilterStart() -local AAASet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterAAA):FilterStart() -local SAMSet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterSAM):FilterStart() -local OpsGroupSet=SET_OPSGROUP:New():FilterActive(true):FilterStart() -self.FlightSet=SET_GROUP:New():FilterCategories({"plane","helicopter"}):FilterStart() -local EngageRange=self.AAARange -local ExceptionSet=self.ExceptionSet -if self.ExceptionSet then -function ExceptionSet:OnAfterAdded(From,Event,To,ObjectName,Object) -BASE:I("TIRESIAS: EXCEPTION Object Added: "..Object:GetName()) -if Object and Object:IsAlive()then -Object.Tiresias={ -type="Exception", -exception=true, -} -Object:SetAIOn() -Object:SetCommandInvisible(false) -Object:EnableEmission(true) -end -end -local OGS=OpsGroupSet:GetAliveSet() -for _,_OG in pairs(OGS or{})do -local OG=_OG -local grp=OG:GetGroup() -ExceptionSet:AddGroup(grp,true) -end -function OpsGroupSet:OnAfterAdded(From,Event,To,ObjectName,Object) -local grp=Object:GetGroup() -ExceptionSet:AddGroup(grp,true) -end -end -function VehicleSet:OnAfterAdded(From,Event,To,ObjectName,Object) -BASE:I("TIRESIAS: VEHCILE Object Added: "..Object:GetName()) -if Object and Object:IsAlive()then -Object:SetAIOff() -Object:SetCommandInvisible(true) -Object.Tiresias={ -type="Vehicle", -invisible=true, -AIOff=true, -exception=false, -} -end -end -local SwitchAAA=self.SwitchAAA -function AAASet:OnAfterAdded(From,Event,To,ObjectName,Object) -if Object and Object:IsAlive()then -BASE:I("TIRESIAS: AAA Object Added: "..Object:GetName()) -Object:OptionEngageRange(EngageRange) -Object:SetCommandInvisible(true) -if SwitchAAA then -Object:SetAIOff() -Object:EnableEmission(false) -end -Object.Tiresias={ -type="AAA", -invisible=true, -range=EngageRange, -exception=false, -AIOff=SwitchAAA, -} -end -end -function SAMSet:OnAfterAdded(From,Event,To,ObjectName,Object) -if Object and Object:IsAlive()then -BASE:I("TIRESIAS: SAM Object Added: "..Object:GetName()) -Object:SetCommandInvisible(true) -Object.Tiresias={ -type="SAM", -invisible=true, -exception=false, -} -end -end -self.VehicleSet=VehicleSet -self.AAASet=AAASet -self.SAMSet=SAMSet -self.OpsGroupSet=OpsGroupSet -self:_InitGroups() -self:__Status(1) -return self -end -function TIRESIAS:onbeforeStatus(From,Event,To) -self:T({From,Event,To}) -if self:GetState()=="Stopped"then -return false -end -return self -end -function TIRESIAS:onafterStatus(From,Event,To) -self:T({From,Event,To}) -if self.debug then -local count=self.VehicleSet:CountAlive() -local AAAcount=self.AAASet:CountAlive() -local SAMcount=self.SAMSet:CountAlive() -local text=string.format("Overall: %d | Vehicles: %d | AAA: %d | SAM: %d",count+AAAcount+SAMcount,count,AAAcount,SAMcount) -self:I(text) -end -self:_InitGroups() -if self.FlightSet:CountAlive()>0 then -local Set=self.FlightSet:GetAliveSet() -for _,_plane in pairs(Set)do -local plane=_plane -local radius=self.PlaneSwitchRange -if plane:IsHelicopter()then -radius=self.HeloSwitchRange -end -self:_SwitchOnGroups(_plane,radius) -end -end -if self:GetState()~="Stopped"then -self:__Status(self.Interval) -end -return self -end -function TIRESIAS:onafterStop(From,Event,To) -self:T({From,Event,To}) -self:UnHandleEvent(EVENTS.PlayerEnterAircraft) -return self -end -STRATEGO={ -ClassName="STRATEGO", -debug=false, -drawzone=false, -markzone=false, -version="0.2.4", -portweight=3, -POIweight=1, -maxrunways=3, -coalition=nil, -colors=nil, -airbasetable={}, -nonconnectedab={}, -easynames={}, -maxdist=150, -disttable={}, -routexists={}, -routefactor=5, -OpsZones={}, -NeutralBenefit=100, -Budget=0, -usebudget=false, -CaptureUnits=3, -CaptureThreatlevel=1, -} -STRATEGO.Type={ -AIRBASE="AIRBASE", -PORT="PORT", -POI="POI", -FARP="FARP", -SHIP="SHIP", -} -function STRATEGO:New(Name,Coalition,MaxDist) -local self=BASE:Inherit(self,FSM:New()) -self.coalition=Coalition -self.coalitiontext=UTILS.GetCoalitionName(Coalition) -self.name=Name or"Hannibal" -self.maxdist=MaxDist or 150 -self.disttable={} -self.routexists={} -self.lid=string.format("STRATEGO %s %s | ",self.name,self.version) -self.bases=SET_AIRBASE:New():FilterOnce() -self.ports=SET_ZONE:New():FilterPrefixes("Port"):FilterOnce() -self.POIs=SET_ZONE:New():FilterPrefixes("POI"):FilterOnce() -self.colors={ -[1]={0,1,0}, -[2]={1,0,0}, -[3]={0,0,1}, -[4]={1,0.65,0}, -} -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","Update","*") -self:AddTransition("*","NodeEvent","*") -self:AddTransition("Running","Stop","Stopped") -return self -end -function STRATEGO:onafterStart(From,Event,To) -self:T(self.lid.."Start") -self:AnalyseBases() -self:AnalysePOIs(self.ports,self.portweight,"PORT") -self:AnalysePOIs(self.POIs,self.POIweight,"POI") -for i=self.maxrunways,1,-1 do -self:AnalyseRoutes(i,i*self.routefactor,self.colors[(i%3)+1],i) -end -self:AnalyseUnconnected(self.colors[4]) -self:I(self.lid.."Advisory ready.") -self:__Update(180) -return self -end -function STRATEGO:onafterUpdate(From,Event,To) -self:T(self.lid.."Update") -self:UpdateNodeCoalitions() -if self:GetState()=="Running"then -self:__Update(180) -end -return self -end -function STRATEGO:SetUsingBudget(Usebudget,StartBudget) -self:T(self.lid.."SetUsingBudget") -self.usebudget=Usebudget -self.Budget=StartBudget -return self -end -function STRATEGO:SetDebug(Debug,DrawZones,MarkZones) -self:T(self.lid.."SetDebug") -self.debug=Debug -self.drawzone=DrawZones -self.markzone=MarkZones -return self -end -function STRATEGO:SetWeights(MaxRunways,PortWeight,POIWeight,RouteFactor) -self:T(self.lid.."SetWeights") -self.portweight=PortWeight or 3 -self.POIweight=POIWeight or 1 -self.maxrunways=MaxRunways or 3 -self.routefactor=RouteFactor or 5 -return self -end -function STRATEGO:SetNeutralBenefit(NeutralBenefit) -self:T(self.lid.."SetNeutralBenefit") -self.NeutralBenefit=NeutralBenefit or 100 -return self -end -function STRATEGO:SetCaptureOptions(CaptureUnits,CaptureThreatlevel) -self:T(self.lid.."SetCaptureOptions") -self.CaptureUnits=CaptureUnits or 3 -self.CaptureThreatlevel=CaptureThreatlevel or 1 -return self -end -function STRATEGO:AnalyseBases() -self:T(self.lid.."AnalyseBases") -local colors=self.colors -local debug=self.debug -local airbasetable=self.airbasetable -local nonconnectedab=self.nonconnectedab -local easynames=self.easynames -self.bases:ForEach( -function(afb) -local ab=afb -local abname=ab:GetName() -local runways=ab:GetRunways() -local numrwys=#runways -if numrwys>=1 then numrwys=numrwys*0.5 end -local abzone=ab:GetZone() -if not abzone then -abzone=ZONE_RADIUS:New(abname,ab:GetVec2(),500) -end -local coa=ab:GetCoalition() -if coa==nil then return end -coa=coa+1 -local abtype="AIRBASE" -if ab:IsShip()then -numrwys=1 -abtype="SHIP" -end -if ab:IsHelipad()then -numrwys=1 -abtype="FARP" -end -local coord=ab:GetCoordinate() -if debug then -abzone:DrawZone(-1,colors[coa],1,colors[coa],0.3,1) -coord:TextToAll(tostring(numrwys),-1,{0,0,0},1,colors[coa],0.3,20) -end -local opszone=self:GetNewOpsZone(abname,coa-1) -local tbl={ -name=abname, -baseweight=numrwys, -weight=0, -coalition=coa-1, -port=false, -zone=abzone, -coord=coord, -type=abtype, -opszone=opszone, -} -airbasetable[abname]=tbl -nonconnectedab[abname]=true -local name=string.gsub(abname,"[%p%s]",".") -easynames[name]=abname -end -) -return self -end -function STRATEGO:UpdateNodeCoalitions() -self:T(self.lid.."UpdateNodeCoalitions") -local newtable={} -for _id,_data in pairs(self.airbasetable)do -local data=_data -if data.type=="AIRBASE"or data.type=="FARP"then -data.coalition=AIRBASE:FindByName(data.name):GetCoalition() -else -data.coalition=data.opszone:GetOwner() -end -newtable[_id]=_data -end -self.airbasetable=nil -self.airbasetable=newtable -return self -end -function STRATEGO:GetNewOpsZone(Zone,Coalition) -self:T(self.lid.."GetNewOpsZone") -local opszone=OPSZONE:New(Zone,Coalition or 0) -opszone:SetCaptureNunits(self.CaptureUnits) -opszone:SetCaptureThreatlevel(self.CaptureThreatlevel) -opszone:SetDrawZone(self.drawzone) -opszone:SetMarkZone(self.markzone) -opszone:Start() -local function Captured(opszone,coalition) -self:__NodeEvent(1,opszone,coalition) -end -function opszone:OnBeforeCaptured(From,Event,To,Coalition) -Captured(opszone,Coalition) -end -return opszone -end -function STRATEGO:AnalysePOIs(Set,Weight,Key) -self:T(self.lid.."AnalysePOIs") -local colors=self.colors -local debug=self.debug -local airbasetable=self.airbasetable -local nonconnectedab=self.nonconnectedab -local easynames=self.easynames -Set:ForEach( -function(port) -local zone=port -local zname=zone:GetName() -local coord=zone:GetCoordinate() -if debug then -zone:DrawZone(-1,colors[1],1,colors[1],0.3,1) -coord:TextToAll(tostring(Weight),-1,{0,0,0},1,colors[1],0.3,20) -end -local opszone=self:GetNewOpsZone(zone) -local tbl={ -name=zname, -baseweight=Weight, -weight=0, -coalition=coalition.side.NEUTRAL, -port=true, -zone=zone, -coord=coord, -type=Key, -opszone=opszone, -} -airbasetable[zone:GetName()]=tbl -nonconnectedab[zone:GetName()]=true -local name=string.gsub(zname,"[%p%s]",".") -easynames[name]=zname -end -) -return self -end -function STRATEGO:GetToFrom(StartPoint,EndPoint) -self:T(self.lid.."GetToFrom") -local pstart=string.gsub(StartPoint,"[%p%s]",".") -local pend=string.gsub(EndPoint,"[%p%s]",".") -local fromto=pstart..";"..pend -local tofrom=pend..";"..pstart -return fromto,tofrom -end -function STRATEGO:AddRoutesManually(Startpoint,Endpoint,Color,Linetype,Draw) -self:T(self.lid.."AddRoutesManually") -local fromto,tofrom=self:GetToFrom(Startpoint,Endpoint) -local startcoordinate=self.airbasetable[Startpoint].coord -local targetcoordinate=self.airbasetable[Endpoint].coord -local dist=UTILS.Round(targetcoordinate:Get2DDistance(startcoordinate),-2)/1000 -local color=Color or{136/255,0,1} -local linetype=Linetype or 5 -local data={ -start=Startpoint, -target=Endpoint, -dist=dist, -} -self.disttable[fromto]=data -self.disttable[tofrom]=data -table.insert(self.routexists,fromto) -table.insert(self.routexists,tofrom) -self.nonconnectedab[Endpoint]=false -self.nonconnectedab[Startpoint]=false -local factor=self.airbasetable[Startpoint].baseweight*self.routefactor -self.airbasetable[Startpoint].weight=self.airbasetable[Startpoint].weight+factor -self.airbasetable[Endpoint].weight=self.airbasetable[Endpoint].weight+factor -if self.debug or Draw then -startcoordinate:LineToAll(targetcoordinate,-1,color,1,linetype,nil,string.format("%dkm",dist)) -end -return self -end -function STRATEGO:AnalyseRoutes(tgtrwys,factor,color,linetype) -self:T(self.lid.."AnalyseRoutes") -for _,_ab in pairs(self.airbasetable)do -if _ab.baseweight>=1 then -local startpoint=_ab.name -local startcoord=_ab.coord -for _,_data in pairs(self.airbasetable)do -local fromto,tofrom=self:GetToFrom(startpoint,_data.name) -if _data.name==startpoint then -elseif _data.baseweight==tgtrwys and not(self.routexists[fromto]or self.routexists[tofrom])then -local tgtc=_data.coord -local dist=UTILS.Round(tgtc:Get2DDistance(startcoord),-2)/1000 -if dist<=self.maxdist then -local data={ -start=startpoint, -target=_data.name, -dist=dist, -} -self.disttable[fromto]=data -self.disttable[tofrom]=data -table.insert(self.routexists,fromto) -table.insert(self.routexists,tofrom) -self.nonconnectedab[_data.name]=false -self.nonconnectedab[startpoint]=false -self.airbasetable[startpoint].weight=self.airbasetable[startpoint].weight+factor -self.airbasetable[_data.name].weight=self.airbasetable[_data.name].weight+factor -if self.debug then -startcoord:LineToAll(tgtc,-1,color,1,linetype,nil,string.format("%dkm",dist)) -end -end -end -end -end -end -return self -end -function STRATEGO:AnalyseUnconnected(Color) -self:T(self.lid.."AnalyseUnconnected") -for _name,_noconnect in pairs(self.nonconnectedab)do -if _noconnect then -local startpoint=_name -local startcoord=self.airbasetable[_name].coord -local shortest=1000*1000 -local closest=nil -local closestcoord=nil -for _,_data in pairs(self.airbasetable)do -if _name~=_data.name then -local tgtc=_data.coord -local dist=UTILS.Round(tgtc:Get2DDistance(startcoord),-2)/1000 -if dist=weight and okay then -weight=_data.weight -if not airbases[weight]then airbases[weight]={}end -table.insert(airbases[weight],_name) -end -end -return airbases[weight],weight -end -function STRATEGO:GetNextHighestWeightNodes(Weight,Coalition) -self:T(self.lid.."GetNextHighestWeightNodes") -local weight=0 -local airbases={} -for _name,_data in pairs(self.airbasetable)do -local okay=true -if Coalition then -if _data.coalition~=Coalition then -okay=false -end -end -if _data.weight>=weight and _data.weight=targetweight)then -shortest=dist -target=cname -weight=self.airbasetable[cname].weight -coa=self.airbasetable[cname].coalition -end -end -end -return shortest,target,weight,coa -end -function STRATEGO:FindClosestStrategicTarget(Startpoint,Weight) -self:T(self.lid.."FindClosestStrategicTarget for "..Startpoint.." Weight "..Weight or 0) -local shortest=1000*1000 -local target=nil -local weight=0 -local coa=nil -if not Weight then Weight=self.maxrunways end -local startpoint=string.gsub(Startpoint,"[%p%s]",".") -for _,_route in pairs(self.routexists)do -if string.find(_route,startpoint,1,true)then -local dist=self.disttable[_route].dist -local tname=string.gsub(_route,startpoint,"") -local tname=string.gsub(tname,";","") -local cname=self.easynames[tname] -local coa=self.airbasetable[cname].coalition -local tweight=self.airbasetable[cname].baseweight -local ttweight=self.airbasetable[cname].weight -self:T("Start -> End: "..startpoint.." -> "..cname) -if(dist=Weight)then -shortest=dist -target=cname -weight=self.airbasetable[cname].weight -coa=self.airbasetable[cname].coalition -end -end -end -return shortest,target,weight,coa -end -function STRATEGO:FindStrategicTargets() -self:T(self.lid.."FindStrategicTargets") -local targets={} -for _,_data in pairs(self.airbasetable)do -local data=_data -if data.coalition==self.coalition then -local dist,name,points,coa=self:FindClosestStrategicTarget(data.name,data.weight) -if coa==coalition.side.NEUTRAL and points~=0 then -local fpoints=points+self.NeutralBenefit -local tries=1 -while targets[fpoints]or tries<100 do -fpoints=points+(self.NeutralBenefit+math.random(1,100)) -tries=tries+1 -end -targets[fpoints]={ -name=name, -dist=dist, -points=fpoints, -coalition=coa, -coalitionname=UTILS.GetCoalitionName(coa), -coordinate=self.airbasetable[name].coord, -} -end -local enemycoa=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE -if coa==enemycoa and points~=0 then -local fpoints=points -local tries=1 -while targets[fpoints]or tries<100 do -fpoints=points+(math.random(1,100)) -tries=tries+1 -end -targets[fpoints]={ -name=name, -dist=dist, -points=fpoints, -coalition=coa, -coalitionname=UTILS.GetCoalitionName(coa), -coordinate=self.airbasetable[name].coord, -} -end -end -end -return targets -end -function STRATEGO:FindConsolidationTargets() -self:T(self.lid.."FindConsolidationTargets") -local targets={} -for _,_data in pairs(self.airbasetable)do -local data=_data -if data.coalition==self.coalition then -local dist,name,points,coa=self:FindClosestConsolidationTarget(data.name,self.maxrunways-1) -if coa==coalition.side.NEUTRAL and points~=0 then -local fpoints=points+self.NeutralBenefit -local tries=1 -while targets[fpoints]or tries<100 do -fpoints=points-(self.NeutralBenefit+math.random(1,100)) -tries=tries+1 -end -targets[fpoints]={ -name=name, -dist=dist, -points=fpoints, -coalition=coa, -coalitionname=UTILS.GetCoalitionName(coa), -coordinate=self.airbasetable[name].coord, -} -end -local enemycoa=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE -if coa==enemycoa and points~=0 then -local fpoints=points -local tries=1 -while targets[fpoints]or tries<100 do -fpoints=points-(math.random(1,100)) -tries=tries+1 -end -targets[fpoints]={ -name=name, -dist=dist, -points=fpoints, -coalition=coa, -coalitionname=UTILS.GetCoalitionName(coa), -coordinate=self.airbasetable[name].coord, -} -end -end -end -return targets -end -function STRATEGO:FindNeighborNodes(Name,Enemies,Friends) -self:T(self.lid.."FindNeighborNodes") -local neighbors={} -local name=string.gsub(Name,"[%p%s]",".") -local shortestdist=1000*1000 -local nearest=nil -for _route,_data in pairs(self.disttable)do -if string.find(_route,name,1,true)then -local dist=self.disttable[_route] -local tname=string.gsub(_route,name,"") -local tname=string.gsub(tname,";","") -local cname=self.easynames[tname] -local encoa=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE -if Enemies==true then -if self.airbasetable[cname].coalition==encoa then -neighbors[cname]=dist -end -elseif Friends==true then -if self.airbasetable[cname].coalition~=encoa then -neighbors[cname]=dist -end -else -neighbors[cname]=dist -end -if neighbors[cname]and dist.distTstop then -self:E(string.format("ERROR: Recovery stop time %s lies before recovery start time %s! Recovery window rejected.",UTILS.SecondsToClock(Tstart),UTILS.SecondsToClock(Tstop))) -return self -end -if Tstop<=Tnow then -string.format("WARNING: Recovery stop time %s already over. Tnow=%s! Recovery window rejected.",UTILS.SecondsToClock(Tstop),UTILS.SecondsToClock(Tnow)) -return self -end -case=case or self.defaultcase -holdingoffset=holdingoffset or self.defaultoffset -if case==1 then -holdingoffset=0 -end -self.windowcount=self.windowcount+1 -local recovery={} -recovery.START=Tstart -recovery.STOP=Tstop -recovery.CASE=case -recovery.OFFSET=holdingoffset -recovery.OPEN=false -recovery.OVER=false -recovery.WIND=turnintowind -recovery.SPEED=speed or 20 -recovery.ID=self.windowcount -if uturn==nil or uturn==true then -recovery.UTURN=true -else -recovery.UTURN=false -end -table.insert(self.recoverytimes,recovery) -return recovery -end -function AIRBOSS:SetSquadronAI(SetGroup) -self.squadsetAI=SetGroup -return self -end -function AIRBOSS:SetExcludeAI(SetGroup) -self.excludesetAI=SetGroup -return self -end -function AIRBOSS:AddExcludeAI(Group) -self.excludesetAI=self.excludesetAI or SET_GROUP:New() -self.excludesetAI:AddGroup(Group) -return self -end -function AIRBOSS:CloseCurrentRecoveryWindow(Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,self.CloseCurrentRecoveryWindow,self) -else -if self:IsRecovering()and self.recoverywindow and self.recoverywindow.OPEN then -self:RecoveryStop() -self.recoverywindow.OPEN=false -self.recoverywindow.OVER=true -self:DeleteRecoveryWindow(self.recoverywindow) -end -end -end -function AIRBOSS:DeleteAllRecoveryWindows(Delay) -for _,recovery in pairs(self.recoverytimes)do -self:I(self.lid..string.format("Deleting recovery window ID %s",tostring(recovery.ID))) -self:DeleteRecoveryWindow(recovery,Delay) -end -return self -end -function AIRBOSS:GetRecoveryWindowByID(id) -if id then -for _,_window in pairs(self.recoverytimes)do -local window=_window -if window and window.ID==id then -return window -end -end -end -return nil -end -function AIRBOSS:DeleteRecoveryWindow(Window,Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,self.DeleteRecoveryWindow,self,Window) -else -for i,_recovery in pairs(self.recoverytimes)do -local recovery=_recovery -if Window and Window.ID==recovery.ID then -if Window.OPEN then -self:RecoveryStop() -else -table.remove(self.recoverytimes,i) -end -end -end -end -end -function AIRBOSS:SetRecoveryTurnTime(Interval) -self.dTturn=Interval or 300 -return self -end -function AIRBOSS:SetMPWireCorrection(Dcorr) -self.mpWireCorrection=Dcorr or 12 -return self -end -function AIRBOSS:SetQueueUpdateTime(TimeInterval) -self.dTqueue=TimeInterval or 30 -return self -end -function AIRBOSS:SetLSOCallInterval(TimeInterval) -self.LSOdT=TimeInterval or 4 -return self -end -function AIRBOSS:SetAirbossNiceGuy(Switch) -if Switch==true or Switch==nil then -self.airbossnice=true -else -self.airbossnice=false -end -return self -end -function AIRBOSS:SetEmergencyLandings(Switch) -if Switch==true or Switch==nil then -self.emergency=true -else -self.emergency=false -end -return self -end -function AIRBOSS:SetDespawnOnEngineShutdown(Switch) -if Switch==true or Switch==nil then -self.despawnshutdown=true -else -self.despawnshutdown=false -end -return self -end -function AIRBOSS:SetRespawnAI(Switch) -if Switch==true or Switch==nil then -self.respawnAI=true -else -self.respawnAI=false -end -return self -end -function AIRBOSS:SetRefuelAI(LowFuelThreshold) -self.lowfuelAI=LowFuelThreshold or 10 -return self -end -function AIRBOSS:SetInitialMaxAlt(MaxAltitude) -self.initialmaxalt=UTILS.FeetToMeters(MaxAltitude or 1300) -return self -end -function AIRBOSS:SetSoundfilesFolder(FolderPath) -if FolderPath then -local lastchar=string.sub(FolderPath,-1) -if lastchar~="/"then -FolderPath=FolderPath.."/" -end -end -self.soundfolder=FolderPath -self:I(self.lid..string.format("Setting sound files folder to: %s",self.soundfolder)) -return self -end -function AIRBOSS:SetStatusUpdateTime(TimeInterval) -self.dTstatus=TimeInterval or 0.5 -return self -end -function AIRBOSS:SetDefaultMessageDuration(Duration) -self.Tmessage=Duration or 10 -return self -end -function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min,High,HIGH,Low,LOW) -if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -self.gle._max=_max or 0.7 -self.gle.High=High or 1.4 -self.gle.HIGH=HIGH or 1.9 -self.gle._min=_min or-0.5 -self.gle.Low=Low or-1.2 -self.gle.LOW=LOW or-1.5 -else -self.gle._max=_max or 0.4 -self.gle.High=High or 0.8 -self.gle.HIGH=HIGH or 1.5 -self.gle._min=_min or-0.3 -self.gle.Low=Low or-0.6 -self.gle.LOW=LOW or-0.9 -end -return self -end -function AIRBOSS:SetLineupErrorThresholds(_max,_min,Left,LeftMed,LEFT,Right,RightMed,RIGHT) -if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -self.lue._max=_max or 1.8 -self.lue._min=_min or-1.8 -self.lue.Left=Left or-2.8 -self.lue.LeftMed=LeftMed or-3.8 -self.lue.LEFT=LEFT or-4.5 -self.lue.Right=Right or 2.8 -self.lue.RightMed=RightMed or 3.8 -self.lue.RIGHT=RIGHT or 4.5 -else -self.lue._max=_max or 0.5 -self.lue._min=_min or-0.5 -self.lue.Left=Left or-1.0 -self.lue.LeftMed=LeftMed or-2.0 -self.lue.LEFT=LEFT or-3.0 -self.lue.Right=Right or 1.0 -self.lue.RightMed=RightMed or 2.0 -self.lue.RIGHT=RIGHT or 3.0 -end -return self -end -function AIRBOSS:SetMarshalRadius(Radius) -self.marshalradius=UTILS.NMToMeters(Radius or 2.8) -return self -end -function AIRBOSS:SetMenuSingleCarrier(Switch) -if Switch==true or Switch==nil then -self.menusingle=true -else -self.menusingle=false -end -return self -end -function AIRBOSS:SetMenuMarkZones(Switch) -if Switch==nil or Switch==true then -self.menumarkzones=true -else -self.menumarkzones=false -end -return self -end -function AIRBOSS:SetMenuSmokeZones(Switch) -if Switch==nil or Switch==true then -self.menusmokezones=true -else -self.menusmokezones=false -end -return self -end -function AIRBOSS:SetTrapSheet(Path,Prefix) -if io then -self.trapsheet=true -self.trappath=Path -self.trapprefix=Prefix -else -self:E(self.lid.."ERROR: io is not desanitized. Cannot save trap sheet.") -end -return self -end -function AIRBOSS:SetStaticWeather(Switch) -if Switch==nil or Switch==true then -self.staticweather=true -else -self.staticweather=false -end -return self -end -function AIRBOSS:SetTACANoff() -self.TACANon=false -return self -end -function AIRBOSS:SetTACAN(Channel,Mode,MorseCode) -self.TACANchannel=Channel or 74 -self.TACANmode=Mode or"X" -self.TACANmorse=MorseCode or"STN" -self.TACANon=true -return self -end -function AIRBOSS:SetICLSoff() -self.ICLSon=false -return self -end -function AIRBOSS:SetICLS(Channel,MorseCode) -self.ICLSchannel=Channel or 1 -self.ICLSmorse=MorseCode or"STN" -self.ICLSon=true -return self -end -function AIRBOSS:SetBeaconRefresh(TimeInterval) -self.dTbeacon=TimeInterval or(20*60) -return self -end -function AIRBOSS:EnableSRS(PathToSRS,Port,Culture,Gender,Voice,GoogleCreds,Volume,AltBackend) -local Frequency=self.AirbossRadio.frequency -local Modulation=self.AirbossRadio.modulation -self.SRS=MSRS:New(PathToSRS,Frequency,Modulation,AltBackend) -self.SRS:SetCoalition(self:GetCoalition()) -self.SRS:SetCoordinate(self:GetCoordinate()) -self.SRS:SetCulture(Culture or"en-US") -self.SRS:SetGender(Gender or"male") -self.SRS:SetPath(PathToSRS) -self.SRS:SetPort(Port or 5002) -self.SRS:SetLabel(self.AirbossRadio.alias or"AIRBOSS") -self.SRS:SetCoordinate(self.carrier:GetCoordinate()) -self.SRS:SetVolume(Volume or 1) -if GoogleCreds then -self.SRS:SetProviderOptionsGoogle(GoogleCreds,GoogleCreds) -self.SRS:SetProvider(MSRS.Provider.GOOGLE) -end -if Voice then -self.SRS:SetVoice(Voice) -end -self.SRS:SetVolume(Volume or 1.0) -self.SRSQ=MSRSQUEUE:New("AIRBOSS") -self.SRSQ:SetTransmitOnlyWithPlayers(true) -if not self.PilotRadio then -self:SetSRSPilotVoice() -end -return self -end -function AIRBOSS:SetLSORadio(Frequency,Modulation,Voice,Gender,Culture) -self.LSOFreq=(Frequency or 264) -Modulation=Modulation or"AM" -if Modulation=="FM"then -self.LSOModu=radio.modulation.FM -else -self.LSOModu=radio.modulation.AM -end -self.LSORadio={} -self.LSORadio.frequency=self.LSOFreq -self.LSORadio.modulation=self.LSOModu -self.LSORadio.alias="LSO" -self.LSORadio.voice=Voice -self.LSORadio.gender=Gender or"male" -self.LSORadio.culture=Culture or"en-US" -return self -end -function AIRBOSS:SetAirbossRadio(Frequency,Modulation,Voice,Gender,Culture) -self.AirbossFreq=Frequency or self:_GetTowerFrequency()or 127.5 -Modulation=Modulation or"AM" -if type(Modulation)=="table"then -self.AirbossModu=Modulation -else -if Modulation=="FM"then -self.AirbossModu=radio.modulation.FM -else -self.AirbossModu=radio.modulation.AM -end -end -self.AirbossRadio={} -self.AirbossRadio.frequency=self.AirbossFreq -self.AirbossRadio.modulation=self.AirbossModu -self.AirbossRadio.alias="AIRBOSS" -self.AirbossRadio.voice=Voice -self.AirbossRadio.gender=Gender or"male" -self.AirbossRadio.culture=Culture or"en-US" -return self -end -function AIRBOSS:SetMarshalRadio(Frequency,Modulation,Voice,Gender,Culture) -self.MarshalFreq=Frequency or 305 -Modulation=Modulation or"AM" -if Modulation=="FM"then -self.MarshalModu=radio.modulation.FM -else -self.MarshalModu=radio.modulation.AM -end -self.MarshalRadio={} -self.MarshalRadio.frequency=self.MarshalFreq -self.MarshalRadio.modulation=self.MarshalModu -self.MarshalRadio.alias="MARSHAL" -self.MarshalRadio.voice=Voice -self.MarshalRadio.gender=Gender or"male" -self.MarshalRadio.culture=Culture or"en-US" -return self -end -function AIRBOSS:SetRadioUnitName(unitname) -self.senderac=unitname -return self -end -function AIRBOSS:SetRadioRelayLSO(unitname) -self.radiorelayLSO=unitname -return self -end -function AIRBOSS:SetRadioRelayMarshal(unitname) -self.radiorelayMSH=unitname -return self -end -function AIRBOSS:SetUserSoundRadio() -self.usersoundradio=true -return self -end -function AIRBOSS:SoundCheckLSO(delay) -if delay and delay>0 then -self:ScheduleOnce(delay,AIRBOSS.SoundCheckLSO,self) -else -local text="Playing LSO sound files:" -for _name,_call in pairs(self.LSOCall)do -local call=_call -text=text..string.format("\nFile=%s.%s, duration=%.2f sec, loud=%s, subtitle=\"%s\".",call.file,call.suffix,call.duration,tostring(call.loud),call.subtitle) -self:RadioTransmission(self.LSORadio,call,false) -if call.loud then -self:RadioTransmission(self.LSORadio,call,true) -end -end -self:T(self.lid..text) -end -end -function AIRBOSS:SoundCheckMarshal(delay) -if delay and delay>0 then -self:ScheduleOnce(delay,AIRBOSS.SoundCheckMarshal,self) -else -local text="Playing Marshal sound files:" -for _name,_call in pairs(self.MarshalCall)do -local call=_call -text=text..string.format("\nFile=%s.%s, duration=%.2f sec, loud=%s, subtitle=\"%s\".",call.file,call.suffix,call.duration,tostring(call.loud),call.subtitle) -self:RadioTransmission(self.MarshalRadio,call,false) -if call.loud then -self:RadioTransmission(self.MarshalRadio,call,true) -end -end -self:T(self.lid..text) -end -end -function AIRBOSS:SetMaxLandingPattern(nmax) -nmax=nmax or 4 -nmax=math.max(nmax,1) -nmax=math.min(nmax,6) -self.Nmaxpattern=nmax -return self -end -function AIRBOSS:SetMaxMarshalStacks(nmax) -self.Nmaxmarshal=nmax or 3 -self.Nmaxmarshal=math.max(self.Nmaxmarshal,1) -return self -end -function AIRBOSS:SetMaxSectionSize(nmax) -nmax=nmax or 2 -nmax=math.max(nmax,1) -nmax=math.min(nmax,4) -self.NmaxSection=nmax-1 -return self -end -function AIRBOSS:SetMaxFlightsPerStack(nmax) -nmax=nmax or 2 -nmax=math.max(nmax,1) -nmax=math.min(nmax,4) -self.NmaxStack=nmax -return self -end -function AIRBOSS:SetHandleAION() -self.handleai=true -return self -end -function AIRBOSS:SetExtraVoiceOvers(status) -self.xtVoiceOvers=status -return self -end -function AIRBOSS:SetExtraVoiceOversAI(status) -self.xtVoiceOversAI=status -return self -end -function AIRBOSS:SetHandleAIOFF() -self.handleai=false -return self -end -function AIRBOSS:SetRecoveryTanker(recoverytanker) -self.tanker=recoverytanker -return self -end -function AIRBOSS:SetAWACS(awacs) -self.awacs=awacs -return self -end -function AIRBOSS:SetDefaultPlayerSkill(skill) -self.defaultskill=skill or AIRBOSS.Difficulty.NORMAL -local gotit=false -for _,_skill in pairs(AIRBOSS.Difficulty)do -if _skill==self.defaultskill then -gotit=true -end -end -if not gotit then -self.defaultskill=AIRBOSS.Difficulty.NORMAL -self:E(self.lid..string.format("ERROR: Invalid default skill = %s. Resetting to Naval Aviator.",tostring(skill))) -end -return self -end -function AIRBOSS:SetAutoSave(path,filename) -self.autosave=true -self.autosavepath=path -self.autosavefile=filename -return self -end -function AIRBOSS:SetDebugModeON() -self.Debug=true -return self -end -function AIRBOSS:SetPatrolAdInfinitum(switch) -if switch==false then -self.adinfinitum=false -else -self.adinfinitum=true -end -return self -end -function AIRBOSS:SetMagneticDeclination(declination) -self.magvar=declination or UTILS.GetMagneticDeclination() -return self -end -function AIRBOSS:SetDebugModeOFF() -self.Debug=false -return self -end -function AIRBOSS:SetFunkManOn(Port,Host) -self.funkmanSocket=SOCKET:New(Port,Host) -return self -end -function AIRBOSS:GetNextRecoveryTime(InSeconds) -if self.recoverywindow then -if InSeconds then -return self.recoverywindow.START,self.recoverywindow.STOP -else -return UTILS.SecondsToClock(self.recoverywindow.START),UTILS.SecondsToClock(self.recoverywindow.STOP) -end -else -if InSeconds then -return-1,-1 -else -return"?","?" -end -end -end -function AIRBOSS:IsRecovering() -return self:is("Recovering") -end -function AIRBOSS:IsIdle() -return self:is("Idle") -end -function AIRBOSS:IsPaused() -return self:is("Paused") -end -function AIRBOSS:_ActivateBeacons() -self:T(self.lid..string.format("Activating Beacons (TACAN=%s, ICLS=%s)",tostring(self.TACANon),tostring(self.ICLSon))) -if self.TACANon then -self:I(self.lid..string.format("Activating TACAN Channel %d%s (%s)",self.TACANchannel,self.TACANmode,self.TACANmorse)) -self.beacon:ActivateTACAN(self.TACANchannel,self.TACANmode,self.TACANmorse,true) -end -if self.ICLSon then -self:I(self.lid..string.format("Activating ICLS Channel %d (%s)",self.ICLSchannel,self.ICLSmorse)) -self.beacon:ActivateICLS(self.ICLSchannel,self.ICLSmorse) -end -self.Tbeacon=timer.getTime() -end -function AIRBOSS:onafterStart(From,Event,To) -self:I(self.lid..string.format("Starting AIRBOSS v%s for carrier unit %s of type %s on map %s",AIRBOSS.version,self.carrier:GetName(),self.carriertype,self.theatre)) -self:_ActivateBeacons() -self.Cposition=self:GetCoordinate() -self.Corientation=self.carrier:GetOrientationX() -self.Corientlast=self.Corientation -self.Tpupdate=timer.getTime() -if#self.recoverytimes==0 and false then -local Topen=timer.getAbsTime()+15*60 -local Tclose=Topen+3*60*60 -self:AddRecoveryWindow(UTILS.SecondsToClock(Topen),UTILS.SecondsToClock(Tclose)) -end -self:_CheckRecoveryTimes() -self.Tqueue=timer.getTime()-60 -self:HandleEvent(EVENTS.Birth) -self:HandleEvent(EVENTS.Land) -self:HandleEvent(EVENTS.EngineShutdown) -self:HandleEvent(EVENTS.Takeoff) -self:HandleEvent(EVENTS.Crash) -self:HandleEvent(EVENTS.Ejection) -self:HandleEvent(EVENTS.PlayerLeaveUnit,self._PlayerLeft) -self:HandleEvent(EVENTS.MissionEnd) -self:HandleEvent(EVENTS.RemoveUnit) -self.StatusTimer=TIMER:New(self._Status,self):Start(2,0.5) -self:__Status(1) -end -function AIRBOSS:onafterStatus(From,Event,To) -local time=timer.getTime() -if time-self.Tqueue>self.dTqueue then -local clock=UTILS.SecondsToClock(timer.getAbsTime()) -local eta=UTILS.SecondsToClock(self:_GetETAatNextWP()) -local hdg=self:GetHeading() -local pos=self:GetCoordinate() -local speed=self.carrier:GetVelocityKNOTS() -local collision=false -local holdtime=0 -if self.holdtimestamp then -holdtime=timer.getTime()-self.holdtimestamp -end -local NextWP=self:_GetNextWaypoint() -local ExpectedSpeed=UTILS.MpsToKnots(NextWP:GetVelocity()) -if speed<0.5 and ExpectedSpeed>0 and not(self.detour or self.turnintowind)then -if not self.holdtimestamp then -self:E(self.lid..string.format("Carrier came to an unexpected standstill. Trying to re-route in 3 min. Speed=%.1f knots, expected=%.1f knots",speed,ExpectedSpeed)) -self.holdtimestamp=timer.getTime() -else -if holdtime>3*60 then -local coord=self:GetCoordinate():Translate(500,hdg+10) -self:CarrierResumeRoute(coord) -self.holdtimestamp=nil -end -end -end -local text=string.format("Time %s - Status %s (case=%d) - Speed=%.1f kts - Heading=%d - WP=%d - ETA=%s - Turning=%s - Collision Warning=%s - Detour=%s - Turn Into Wind=%s - Holdtime=%d sec",clock,self:GetState(),self.case,speed,hdg,self.currentwp,eta,tostring(self.turning),tostring(collision),tostring(self.detour),tostring(self.turnintowind),holdtime) -self:T(self.lid..text) -text="Players:" -local i=0 -for _name,_player in pairs(self.players)do -i=i+1 -local player=_player -text=text..string.format("\n%d.) %s: Step=%s, Unit=%s, Airframe=%s",i,tostring(player.name),tostring(player.step),tostring(player.unitname),tostring(player.actype)) -end -if i==0 then -text=text.." none" -end -self:T(self.lid..text) -if collision then -if self.turnintowind then -self:CarrierResumeRoute(self.Creturnto) -if self:IsRecovering()and self.recoverywindow and self.recoverywindow.WIND then -self.recoverywindow.WIND=false -end -end -end -self:_CheckRecoveryTimes() -self:_ScanCarrierZone() -self:_CheckQueue() -self:_CheckCarrierTurning() -self:_CheckPatternUpdate() -self.Tqueue=time -end -if time-self.Tbeacon>self.dTbeacon then -self:_ActivateBeacons() -end -self:__Status(-30) -end -function AIRBOSS:_Status() -self:_CheckPlayerStatus() -self:_CheckAIStatus() -end -function AIRBOSS:_CheckAIStatus() -for _,_flight in pairs(self.Qmarshal)do -local flight=_flight -if flight.ai then -local fuel=flight.group:GetFuelMin()*100 -local text=string.format("Group %s fuel=%.1f %%",flight.groupname,fuel) -self:T3(self.lid..text) -if self.lowfuelAI and fuel=recovery.START then -if time0 then -local extmin=5*npattern -recovery.STOP=recovery.STOP+extmin*60 -local text=string.format("We still got flights in the pattern.\nRecovery time prolonged by %d minutes.\nNow get your act together and no more bolters!",extmin) -self:MessageToPattern(text,"AIRBOSS","99",10,false,nil) -else -self:RecoveryStop() -state="closing now" -recovery.OPEN=false -recovery.OVER=true -end -else -state="closed" -end -end -else -state="in the future" -if nextwindow==nil then -nextwindow=recovery -state="next in line" -end -end -text=text..string.format("\n- Start=%s Stop=%s Case=%d Offset=%d Open=%s Closed=%s Status=\"%s\"",Cstart,Cstop,recovery.CASE,recovery.OFFSET,tostring(recovery.OPEN),tostring(recovery.OVER),state) -end -self:T(self.lid..text) -self.recoverywindow=nil -if self:IsIdle()then -if nextwindow then -self:RecoveryCase(nextwindow.CASE,nextwindow.OFFSET) -if nextwindow.WIND and nextwindow.START-time5 -local _,vwind=self:GetWind() -if vwind<0.1 then -uturn=false -end -if not nextwindow.UTURN then -uturn=false -end -self:T(self.lid..string.format("Heading=%03d°, Wind=%03d° %.1f kts, Delta=%03d° ==> U-turn=%s",hdg,wind,UTILS.MpsToKnots(vwind),delta,tostring(uturn))) -local t=math.max(nextwindow.STOP-nextwindow.START+self.dTturn,60*60*24) -local v=UTILS.KnotsToMps(nextwindow.SPEED) -local vmax=self.carrier:GetSpeedMax()/3.6 -v=math.min(v,vmax) -self:CarrierTurnIntoWind(t,v,uturn) -end -self.recoverywindow=nextwindow -else -self:RecoveryCase() -end -else -if currwindow then -self.recoverywindow=currwindow -else -self.recoverywindow=nextwindow -end -end -self:T2({"FF",recoverywindow=self.recoverywindow}) -end -function AIRBOSS:_GetFlightLead(flight) -if flight.name~=flight.seclead then -local lead=self.players[flight.seclead] -return lead,false -else -return flight,true -end -end -function AIRBOSS:onbeforeRecoveryCase(From,Event,To,Case,Offset) -Case=Case or self.defaultcase -Offset=Offset or self.defaultoffset -if Case==self.case and Offset==self.holdingoffset then -return false -end -return true -end -function AIRBOSS:onafterRecoveryCase(From,Event,To,Case,Offset) -Case=Case or self.defaultcase -Offset=Offset or self.defaultoffset -local text=string.format("Switching recovery case %d ==> %d",self.case,Case) -if Case>1 then -text=text..string.format(" Holding offset angle %d degrees.",Offset) -end -MESSAGE:New(text,20,self.alias):ToAllIf(self.Debug) -self:T(self.lid..text) -self.case=Case -self.holdingoffset=Offset -for _,_flight in pairs(self.flights)do -local flight=_flight -if not(self:_InQueue(self.Qmarshal,flight.group)or self:_InQueue(self.Qpattern,flight.group))then -if flight.name~=flight.seclead then -local lead=self.players[flight.seclead] -if lead and not(self:_InQueue(self.Qmarshal,lead.group)or self:_InQueue(self.Qpattern,lead.group))then -flight.case=self.case -end -else -flight.case=self.case -end -end -end -end -function AIRBOSS:onafterRecoveryStart(From,Event,To,Case,Offset) -Case=Case or self.defaultcase -Offset=Offset or self.defaultoffset -self:_MarshalCallRecoveryStart(Case) -self:RecoveryCase(Case,Offset) -end -function AIRBOSS:onafterRecoveryStop(From,Event,To) -self:T(self.lid..string.format("Stopping aircraft recovery.")) -self:_MarshalCallRecoveryStopped(self.case) -if self.turnintowind then -local coord=self.Creturnto -if self.recoverywindow and self.recoverywindow.UTURN==false then -coord=nil -end -self:CarrierResumeRoute(coord) -end -if self.recoverywindow and self.recoverywindow.OPEN==true then -self.recoverywindow.OPEN=false -self.recoverywindow.OVER=true -self:DeleteRecoveryWindow(self.recoverywindow) -end -self:_CheckRecoveryTimes() -end -function AIRBOSS:onafterRecoveryPause(From,Event,To,duration) -self:T(self.lid..string.format("Pausing aircraft recovery.")) -if duration then -self:__RecoveryUnpause(duration) -local clock=UTILS.SecondsToClock(timer.getAbsTime()+duration) -self:_MarshalCallRecoveryPausedResumedAt(clock) -else -local text=string.format("aircraft recovery is paused until further notice.") -self:_MarshalCallRecoveryPausedNotice() -end -end -function AIRBOSS:onafterRecoveryUnpause(From,Event,To) -self:T(self.lid..string.format("Unpausing aircraft recovery.")) -self:_MarshalCallResumeRecovery() -end -function AIRBOSS:onafterPassingWaypoint(From,Event,To,n) -self:I(self.lid..string.format("Carrier passed waypoint %d.",n)) -end -function AIRBOSS:onafterIdle(From,Event,To) -self:T(self.lid..string.format("Carrier goes to idle.")) -end -function AIRBOSS:onafterStop(From,Event,To) -self:I(self.lid..string.format("Stopping airboss script.")) -self:UnHandleEvent(EVENTS.Birth) -self:UnHandleEvent(EVENTS.Land) -self:UnHandleEvent(EVENTS.EngineShutdown) -self:UnHandleEvent(EVENTS.Takeoff) -self:UnHandleEvent(EVENTS.Crash) -self:UnHandleEvent(EVENTS.Ejection) -self:UnHandleEvent(EVENTS.PlayerLeaveUnit) -self:UnHandleEvent(EVENTS.MissionEnd) -self.CallScheduler:Clear() -end -function AIRBOSS:_InitStennis() -self.carrierparam.sterndist=-153 -self.carrierparam.deckheight=18.30 -self.carrierparam.totlength=310 -self.carrierparam.totwidthport=40 -self.carrierparam.totwidthstarboard=30 -self.carrierparam.rwyangle=-9.1359 -self.carrierparam.rwylength=225 -self.carrierparam.rwywidth=20 -self.carrierparam.wire1=46 -self.carrierparam.wire2=46+12 -self.carrierparam.wire3=46+24 -self.carrierparam.wire4=46+35 -self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 -self.Platform.name="Platform 5k" -self.Platform.Xmin=-UTILS.NMToMeters(22) -self.Platform.Xmax=nil -self.Platform.Zmin=-UTILS.NMToMeters(30) -self.Platform.Zmax=UTILS.NMToMeters(30) -self.Platform.LimitXmin=nil -self.Platform.LimitXmax=nil -self.Platform.LimitZmin=nil -self.Platform.LimitZmax=nil -self.DirtyUp.name="Dirty Up" -self.DirtyUp.Xmin=-UTILS.NMToMeters(21) -self.DirtyUp.Xmax=nil -self.DirtyUp.Zmin=-UTILS.NMToMeters(30) -self.DirtyUp.Zmax=UTILS.NMToMeters(30) -self.DirtyUp.LimitXmin=nil -self.DirtyUp.LimitXmax=nil -self.DirtyUp.LimitZmin=nil -self.DirtyUp.LimitZmax=nil -self.Bullseye.name="Bullseye" -self.Bullseye.Xmin=-UTILS.NMToMeters(11) -self.Bullseye.Xmax=nil -self.Bullseye.Zmin=-UTILS.NMToMeters(30) -self.Bullseye.Zmax=UTILS.NMToMeters(30) -self.Bullseye.LimitXmin=nil -self.Bullseye.LimitXmax=nil -self.Bullseye.LimitZmin=nil -self.Bullseye.LimitZmax=nil -self.BreakEntry.name="Break Entry" -self.BreakEntry.Xmin=-UTILS.NMToMeters(4) -self.BreakEntry.Xmax=nil -self.BreakEntry.Zmin=-UTILS.NMToMeters(0.5) -self.BreakEntry.Zmax=UTILS.NMToMeters(1.5) -self.BreakEntry.LimitXmin=0 -self.BreakEntry.LimitXmax=nil -self.BreakEntry.LimitZmin=nil -self.BreakEntry.LimitZmax=nil -self.BreakEarly.name="Early Break" -self.BreakEarly.Xmin=-UTILS.NMToMeters(1) -self.BreakEarly.Xmax=UTILS.NMToMeters(5) -self.BreakEarly.Zmin=-UTILS.NMToMeters(2) -self.BreakEarly.Zmax=UTILS.NMToMeters(1) -self.BreakEarly.LimitXmin=0 -self.BreakEarly.LimitXmax=nil -self.BreakEarly.LimitZmin=-UTILS.NMToMeters(0.2) -self.BreakEarly.LimitZmax=nil -self.BreakLate.name="Late Break" -self.BreakLate.Xmin=-UTILS.NMToMeters(1) -self.BreakLate.Xmax=UTILS.NMToMeters(5) -self.BreakLate.Zmin=-UTILS.NMToMeters(2) -self.BreakLate.Zmax=UTILS.NMToMeters(1) -self.BreakLate.LimitXmin=0 -self.BreakLate.LimitXmax=nil -self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.8) -self.BreakLate.LimitZmax=nil -self.Abeam.name="Abeam Position" -self.Abeam.Xmin=-UTILS.NMToMeters(5) -self.Abeam.Xmax=UTILS.NMToMeters(5) -self.Abeam.Zmin=-UTILS.NMToMeters(2) -self.Abeam.Zmax=500 -self.Abeam.LimitXmin=-200 -self.Abeam.LimitXmax=nil -self.Abeam.LimitZmin=nil -self.Abeam.LimitZmax=nil -self.Ninety.name="Ninety" -self.Ninety.Xmin=-UTILS.NMToMeters(4) -self.Ninety.Xmax=0 -self.Ninety.Zmin=-UTILS.NMToMeters(2) -self.Ninety.Zmax=nil -self.Ninety.LimitXmin=nil -self.Ninety.LimitXmax=nil -self.Ninety.LimitZmin=nil -self.Ninety.LimitZmax=-UTILS.NMToMeters(0.6) -self.Wake.name="Wake" -self.Wake.Xmin=-UTILS.NMToMeters(4) -self.Wake.Xmax=0 -self.Wake.Zmin=-2000 -self.Wake.Zmax=nil -self.Wake.LimitXmin=nil -self.Wake.LimitXmax=nil -self.Wake.LimitZmin=0 -self.Wake.LimitZmax=nil -self.Final.name="Final" -self.Final.Xmin=-UTILS.NMToMeters(4) -self.Final.Xmax=0 -self.Final.Zmin=-2000 -self.Final.Zmax=nil -self.Final.LimitXmin=nil -self.Final.LimitXmax=nil -self.Final.LimitZmin=nil -self.Final.LimitZmax=nil -self.Groove.name="Groove" -self.Groove.Xmin=-UTILS.NMToMeters(4) -self.Groove.Xmax=nil -self.Groove.Zmin=-UTILS.NMToMeters(2) -self.Groove.Zmax=UTILS.NMToMeters(2) -self.Groove.LimitXmin=nil -self.Groove.LimitXmax=nil -self.Groove.LimitZmin=nil -self.Groove.LimitZmax=nil -end -function AIRBOSS:_InitNimitz() -self:_InitStennis() -self.carrierparam.sterndist=-164 -self.carrierparam.deckheight=20.1494 -self.carrierparam.totlength=332.8 -self.carrierparam.totwidthport=45 -self.carrierparam.totwidthstarboard=35 -self.carrierparam.rwyangle=-9.1359 -self.carrierparam.rwylength=250 -self.carrierparam.rwywidth=25 -self.carrierparam.wire1=55 -self.carrierparam.wire2=67 -self.carrierparam.wire3=79 -self.carrierparam.wire4=92 -self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 -end -function AIRBOSS:_InitForrestal() -self:_InitNimitz() -self.carrierparam.sterndist=-135.5 -self.carrierparam.deckheight=20 -self.carrierparam.totlength=315 -self.carrierparam.totwidthport=45 -self.carrierparam.totwidthstarboard=35 -self.carrierparam.rwyangle=-9.1359 -self.carrierparam.rwylength=212 -self.carrierparam.rwywidth=25 -self.carrierparam.wire1=44 -self.carrierparam.wire2=54 -self.carrierparam.wire3=64 -self.carrierparam.wire4=74 -self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 -end -function AIRBOSS:_InitHermes() -self:_InitStennis() -self.carrierparam.sterndist=-105 -self.carrierparam.deckheight=12 -self.carrierparam.totlength=228.19 -self.carrierparam.totwidthport=20.5 -self.carrierparam.totwidthstarboard=24.5 -self.carrierparam.rwyangle=0 -self.carrierparam.rwylength=215 -self.carrierparam.rwywidth=13 -self.carrierparam.wire1=nil -self.carrierparam.wire2=nil -self.carrierparam.wire3=nil -self.carrierparam.wire4=nil -self.carrierparam.landingspot=69 -self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot -self.BreakLate.name="Late Break" -self.BreakLate.Xmin=-UTILS.NMToMeters(1) -self.BreakLate.Xmax=UTILS.NMToMeters(5) -self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) -self.BreakLate.Zmax=UTILS.NMToMeters(1) -self.BreakLate.LimitXmin=0 -self.BreakLate.LimitXmax=nil -self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -self.BreakLate.LimitZmax=nil -end -function AIRBOSS:_InitInvincible() -self:_InitStennis() -self.carrierparam.sterndist=-105 -self.carrierparam.deckheight=12 -self.carrierparam.totlength=228.19 -self.carrierparam.totwidthport=20.5 -self.carrierparam.totwidthstarboard=24.5 -self.carrierparam.rwyangle=0 -self.carrierparam.rwylength=215 -self.carrierparam.rwywidth=13 -self.carrierparam.wire1=nil -self.carrierparam.wire2=nil -self.carrierparam.wire3=nil -self.carrierparam.wire4=nil -self.carrierparam.landingspot=69 -self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot -self.BreakLate.name="Late Break" -self.BreakLate.Xmin=-UTILS.NMToMeters(1) -self.BreakLate.Xmax=UTILS.NMToMeters(5) -self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) -self.BreakLate.Zmax=UTILS.NMToMeters(1) -self.BreakLate.LimitXmin=0 -self.BreakLate.LimitXmax=nil -self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -self.BreakLate.LimitZmax=nil -end -function AIRBOSS:_InitTarawa() -self:_InitStennis() -self.carrierparam.sterndist=-125 -self.carrierparam.deckheight=21 -self.carrierparam.totlength=245 -self.carrierparam.totwidthport=10 -self.carrierparam.totwidthstarboard=25 -self.carrierparam.rwyangle=0 -self.carrierparam.rwylength=225 -self.carrierparam.rwywidth=15 -self.carrierparam.wire1=nil -self.carrierparam.wire2=nil -self.carrierparam.wire3=nil -self.carrierparam.wire4=nil -self.carrierparam.landingspot=57 -self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot -self.BreakLate.name="Late Break" -self.BreakLate.Xmin=-UTILS.NMToMeters(1) -self.BreakLate.Xmax=UTILS.NMToMeters(5) -self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) -self.BreakLate.Zmax=UTILS.NMToMeters(1) -self.BreakLate.LimitXmin=0 -self.BreakLate.LimitXmax=nil -self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -self.BreakLate.LimitZmax=nil -end -function AIRBOSS:_InitAmerica() -self:_InitStennis() -self.carrierparam.sterndist=-125 -self.carrierparam.deckheight=20 -self.carrierparam.totlength=257 -self.carrierparam.totwidthport=11 -self.carrierparam.totwidthstarboard=25 -self.carrierparam.rwyangle=0 -self.carrierparam.rwylength=240 -self.carrierparam.rwywidth=15 -self.carrierparam.wire1=nil -self.carrierparam.wire2=nil -self.carrierparam.wire3=nil -self.carrierparam.wire4=nil -self.carrierparam.landingspot=59 -self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot -self.BreakLate.name="Late Break" -self.BreakLate.Xmin=-UTILS.NMToMeters(1) -self.BreakLate.Xmax=UTILS.NMToMeters(5) -self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) -self.BreakLate.Zmax=UTILS.NMToMeters(1) -self.BreakLate.LimitXmin=0 -self.BreakLate.LimitXmax=nil -self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -self.BreakLate.LimitZmax=nil -end -function AIRBOSS:_InitJcarlos() -self:_InitStennis() -self.carrierparam.sterndist=-125 -self.carrierparam.deckheight=20 -self.carrierparam.totlength=231 -self.carrierparam.totwidthport=10 -self.carrierparam.totwidthstarboard=22 -self.carrierparam.rwyangle=0 -self.carrierparam.rwylength=202 -self.carrierparam.rwywidth=14 -self.carrierparam.wire1=nil -self.carrierparam.wire2=nil -self.carrierparam.wire3=nil -self.carrierparam.wire4=nil -self.carrierparam.landingspot=89 -self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot -self.BreakLate.name="Late Break" -self.BreakLate.Xmin=-UTILS.NMToMeters(1) -self.BreakLate.Xmax=UTILS.NMToMeters(5) -self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) -self.BreakLate.Zmax=UTILS.NMToMeters(1) -self.BreakLate.LimitXmin=0 -self.BreakLate.LimitXmax=nil -self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -self.BreakLate.LimitZmax=nil -end -function AIRBOSS:_InitCanberra() -self:_InitJcarlos() -end -function AIRBOSS:SetVoiceOversMarshalByGabriella(mizfolder) -if mizfolder then -local lastchar=string.sub(mizfolder,-1) -if lastchar~="/"then -mizfolder=mizfolder.."/" -end -self.soundfolderMSH=mizfolder -else -self.soundfolderMSH=self.soundfolder -end -self:I(self.lid..string.format("Marshal Gabriella reporting for duty! Soundfolder=%s",tostring(self.soundfolderMSH))) -self.MarshalCall.AFFIRMATIVE.duration=0.65 -self.MarshalCall.ALTIMETER.duration=0.60 -self.MarshalCall.BRC.duration=0.67 -self.MarshalCall.CARRIERTURNTOHEADING.duration=1.62 -self.MarshalCall.CASE.duration=0.30 -self.MarshalCall.CHARLIETIME.duration=0.77 -self.MarshalCall.CLEAREDFORRECOVERY.duration=0.93 -self.MarshalCall.DECKCLOSED.duration=0.73 -self.MarshalCall.DEGREES.duration=0.48 -self.MarshalCall.EXPECTED.duration=0.50 -self.MarshalCall.FLYNEEDLES.duration=0.89 -self.MarshalCall.HOLDATANGELS.duration=0.81 -self.MarshalCall.HOURS.duration=0.41 -self.MarshalCall.MARSHALRADIAL.duration=0.95 -self.MarshalCall.N0.duration=0.41 -self.MarshalCall.N1.duration=0.30 -self.MarshalCall.N2.duration=0.34 -self.MarshalCall.N3.duration=0.31 -self.MarshalCall.N4.duration=0.34 -self.MarshalCall.N5.duration=0.30 -self.MarshalCall.N6.duration=0.33 -self.MarshalCall.N7.duration=0.38 -self.MarshalCall.N8.duration=0.35 -self.MarshalCall.N9.duration=0.35 -self.MarshalCall.NEGATIVE.duration=0.60 -self.MarshalCall.NEWFB.duration=0.95 -self.MarshalCall.OPS.duration=0.23 -self.MarshalCall.POINT.duration=0.38 -self.MarshalCall.RADIOCHECK.duration=1.27 -self.MarshalCall.RECOVERY.duration=0.60 -self.MarshalCall.RECOVERYOPSSTOPPED.duration=1.25 -self.MarshalCall.RECOVERYPAUSEDNOTICE.duration=2.55 -self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=2.55 -self.MarshalCall.REPORTSEEME.duration=0.87 -self.MarshalCall.RESUMERECOVERY.duration=1.55 -self.MarshalCall.ROGER.duration=0.50 -self.MarshalCall.SAYNEEDLES.duration=0.82 -self.MarshalCall.STACKFULL.duration=5.70 -self.MarshalCall.STARTINGRECOVERY.duration=1.61 -end -function AIRBOSS:SetVoiceOversMarshalByRaynor(mizfolder) -if mizfolder then -local lastchar=string.sub(mizfolder,-1) -if lastchar~="/"then -mizfolder=mizfolder.."/" -end -self.soundfolderMSH=mizfolder -else -self.soundfolderMSH=self.soundfolder -end -self:I(self.lid..string.format("Marshal Raynor reporting for duty! Soundfolder=%s",tostring(self.soundfolderMSH))) -self.MarshalCall.AFFIRMATIVE.duration=0.70 -self.MarshalCall.ALTIMETER.duration=0.60 -self.MarshalCall.BRC.duration=0.60 -self.MarshalCall.CARRIERTURNTOHEADING.duration=1.87 -self.MarshalCall.CASE.duration=0.60 -self.MarshalCall.CHARLIETIME.duration=0.81 -self.MarshalCall.CLEAREDFORRECOVERY.duration=1.21 -self.MarshalCall.DECKCLOSED.duration=0.86 -self.MarshalCall.DEGREES.duration=0.55 -self.MarshalCall.EXPECTED.duration=0.61 -self.MarshalCall.FLYNEEDLES.duration=0.90 -self.MarshalCall.HOLDATANGELS.duration=0.91 -self.MarshalCall.HOURS.duration=0.54 -self.MarshalCall.MARSHALRADIAL.duration=0.80 -self.MarshalCall.N0.duration=0.38 -self.MarshalCall.N1.duration=0.30 -self.MarshalCall.N2.duration=0.30 -self.MarshalCall.N3.duration=0.30 -self.MarshalCall.N4.duration=0.32 -self.MarshalCall.N5.duration=0.41 -self.MarshalCall.N6.duration=0.48 -self.MarshalCall.N7.duration=0.51 -self.MarshalCall.N8.duration=0.38 -self.MarshalCall.N9.duration=0.34 -self.MarshalCall.NEGATIVE.duration=0.60 -self.MarshalCall.NEWFB.duration=1.10 -self.MarshalCall.OPS.duration=0.46 -self.MarshalCall.POINT.duration=0.21 -self.MarshalCall.RADIOCHECK.duration=0.95 -self.MarshalCall.RECOVERY.duration=0.63 -self.MarshalCall.RECOVERYOPSSTOPPED.duration=1.36 -self.MarshalCall.RECOVERYPAUSEDNOTICE.duration=2.8 -self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=2.75 -self.MarshalCall.REPORTSEEME.duration=1.06 -self.MarshalCall.RESUMERECOVERY.duration=1.41 -self.MarshalCall.ROGER.duration=0.41 -self.MarshalCall.SAYNEEDLES.duration=0.79 -self.MarshalCall.STACKFULL.duration=4.70 -self.MarshalCall.STARTINGRECOVERY.duration=2.06 -end -function AIRBOSS:SetVoiceOversLSOByRaynor(mizfolder) -if mizfolder then -local lastchar=string.sub(mizfolder,-1) -if lastchar~="/"then -mizfolder=mizfolder.."/" -end -self.soundfolderLSO=mizfolder -else -self.soundfolderLSO=self.soundfolder -end -self:I(self.lid..string.format("LSO Raynor reporting for duty! Soundfolder=%s",tostring(self.soundfolderLSO))) -self.LSOCall.BOLTER.duration=0.75 -self.LSOCall.CALLTHEBALL.duration=0.625 -self.LSOCall.CHECK.duration=0.40 -self.LSOCall.CLEAREDTOLAND.duration=0.85 -self.LSOCall.COMELEFT.duration=0.60 -self.LSOCall.DEPARTANDREENTER.duration=1.10 -self.LSOCall.EXPECTHEAVYWAVEOFF.duration=1.30 -self.LSOCall.EXPECTSPOT75.duration=1.85 -self.LSOCall.EXPECTSPOT5.duration=1.3 -self.LSOCall.FAST.duration=0.75 -self.LSOCall.FOULDECK.duration=0.75 -self.LSOCall.HIGH.duration=0.65 -self.LSOCall.IDLE.duration=0.40 -self.LSOCall.LONGINGROOVE.duration=1.25 -self.LSOCall.LOW.duration=0.60 -self.LSOCall.N0.duration=0.38 -self.LSOCall.N1.duration=0.30 -self.LSOCall.N2.duration=0.30 -self.LSOCall.N3.duration=0.30 -self.LSOCall.N4.duration=0.32 -self.LSOCall.N5.duration=0.41 -self.LSOCall.N6.duration=0.48 -self.LSOCall.N7.duration=0.51 -self.LSOCall.N8.duration=0.38 -self.LSOCall.N9.duration=0.34 -self.LSOCall.PADDLESCONTACT.duration=0.91 -self.LSOCall.POWER.duration=0.45 -self.LSOCall.RADIOCHECK.duration=0.90 -self.LSOCall.RIGHTFORLINEUP.duration=0.70 -self.LSOCall.ROGERBALL.duration=0.72 -self.LSOCall.SLOW.duration=0.63 -self.LSOCall.STABILIZED.duration=0.75 -self.LSOCall.WAVEOFF.duration=0.55 -self.LSOCall.WELCOMEABOARD.duration=0.80 -end -function AIRBOSS:SetVoiceOversLSOByFF(mizfolder) -if mizfolder then -local lastchar=string.sub(mizfolder,-1) -if lastchar~="/"then -mizfolder=mizfolder.."/" -end -self.soundfolderLSO=mizfolder -else -self.soundfolderLSO=self.soundfolder -end -self:I(self.lid..string.format("LSO FF reporting for duty! Soundfolder=%s",tostring(self.soundfolderLSO))) -self.LSOCall.BOLTER.duration=0.75 -self.LSOCall.CALLTHEBALL.duration=0.60 -self.LSOCall.CHECK.duration=0.45 -self.LSOCall.CLEAREDTOLAND.duration=1.00 -self.LSOCall.COMELEFT.duration=0.60 -self.LSOCall.DEPARTANDREENTER.duration=1.10 -self.LSOCall.EXPECTHEAVYWAVEOFF.duration=1.20 -self.LSOCall.EXPECTSPOT75.duration=2.00 -self.LSOCall.EXPECTSPOT5.duration=1.3 -self.LSOCall.FAST.duration=0.70 -self.LSOCall.FOULDECK.duration=0.62 -self.LSOCall.HIGH.duration=0.65 -self.LSOCall.IDLE.duration=0.45 -self.LSOCall.LONGINGROOVE.duration=1.20 -self.LSOCall.LOW.duration=0.50 -self.LSOCall.N0.duration=0.40 -self.LSOCall.N1.duration=0.25 -self.LSOCall.N2.duration=0.37 -self.LSOCall.N3.duration=0.37 -self.LSOCall.N4.duration=0.39 -self.LSOCall.N5.duration=0.39 -self.LSOCall.N6.duration=0.40 -self.LSOCall.N7.duration=0.40 -self.LSOCall.N8.duration=0.37 -self.LSOCall.N9.duration=0.40 -self.LSOCall.PADDLESCONTACT.duration=1.00 -self.LSOCall.POWER.duration=0.50 -self.LSOCall.RADIOCHECK.duration=1.10 -self.LSOCall.RIGHTFORLINEUP.duration=0.80 -self.LSOCall.ROGERBALL.duration=1.00 -self.LSOCall.SLOW.duration=0.65 -self.LSOCall.SLOW.duration=0.59 -self.LSOCall.STABILIZED.duration=0.90 -self.LSOCall.WAVEOFF.duration=0.60 -self.LSOCall.WELCOMEABOARD.duration=1.00 -end -function AIRBOSS:SetVoiceOversMarshalByFF(mizfolder) -if mizfolder then -local lastchar=string.sub(mizfolder,-1) -if lastchar~="/"then -mizfolder=mizfolder.."/" -end -self.soundfolderMSH=mizfolder -else -self.soundfolderMSH=self.soundfolder -end -self:I(self.lid..string.format("Marshal FF reporting for duty! Soundfolder=%s",tostring(self.soundfolderMSH))) -self.MarshalCall.AFFIRMATIVE.duration=0.90 -self.MarshalCall.ALTIMETER.duration=0.85 -self.MarshalCall.BRC.duration=0.80 -self.MarshalCall.CARRIERTURNTOHEADING.duration=2.48 -self.MarshalCall.CASE.duration=0.40 -self.MarshalCall.CHARLIETIME.duration=0.90 -self.MarshalCall.CLEAREDFORRECOVERY.duration=1.25 -self.MarshalCall.DECKCLOSED.duration=1.10 -self.MarshalCall.DEGREES.duration=0.60 -self.MarshalCall.EXPECTED.duration=0.55 -self.MarshalCall.FLYNEEDLES.duration=0.90 -self.MarshalCall.HOLDATANGELS.duration=1.10 -self.MarshalCall.HOURS.duration=0.60 -self.MarshalCall.MARSHALRADIAL.duration=1.10 -self.MarshalCall.N0.duration=0.40 -self.MarshalCall.N1.duration=0.25 -self.MarshalCall.N2.duration=0.37 -self.MarshalCall.N3.duration=0.37 -self.MarshalCall.N4.duration=0.39 -self.MarshalCall.N5.duration=0.39 -self.MarshalCall.N6.duration=0.40 -self.MarshalCall.N7.duration=0.40 -self.MarshalCall.N8.duration=0.37 -self.MarshalCall.N9.duration=0.40 -self.MarshalCall.NEGATIVE.duration=0.80 -self.MarshalCall.NEWFB.duration=1.35 -self.MarshalCall.OPS.duration=0.48 -self.MarshalCall.POINT.duration=0.33 -self.MarshalCall.RADIOCHECK.duration=1.20 -self.MarshalCall.RECOVERY.duration=0.70 -self.MarshalCall.RECOVERYOPSSTOPPED.duration=1.65 -self.MarshalCall.RECOVERYPAUSEDNOTICE.duration=2.9 -self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=3.40 -self.MarshalCall.REPORTSEEME.duration=0.95 -self.MarshalCall.RESUMERECOVERY.duration=1.75 -self.MarshalCall.ROGER.duration=0.53 -self.MarshalCall.SAYNEEDLES.duration=0.90 -self.MarshalCall.STACKFULL.duration=6.35 -self.MarshalCall.STARTINGRECOVERY.duration=2.65 -end -function AIRBOSS:_InitVoiceOvers() -self.LSOCall={ -BOLTER={file="LSO-BolterBolter",suffix="ogg",loud=false,subtitle="Bolter, Bolter",duration=0.75,subduration=5}, -CALLTHEBALL={file="LSO-CallTheBall",suffix="ogg",loud=false,subtitle="Call the ball",duration=0.6,subduration=2}, -CHECK={file="LSO-Check",suffix="ogg",loud=false,subtitle="Check",duration=0.45,subduration=2.5}, -CLEAREDTOLAND={file="LSO-ClearedToLand",suffix="ogg",loud=false,subtitle="Cleared to land",duration=1.0,subduration=5}, -COMELEFT={file="LSO-ComeLeft",suffix="ogg",loud=true,subtitle="Come left",duration=0.60,subduration=1}, -RADIOCHECK={file="LSO-RadioCheck",suffix="ogg",loud=false,subtitle="Paddles, radio check",duration=1.1,subduration=5}, -RIGHTFORLINEUP={file="LSO-RightForLineup",suffix="ogg",loud=true,subtitle="Right for line up",duration=0.80,subduration=1}, -HIGH={file="LSO-High",suffix="ogg",loud=true,subtitle="You're high",duration=0.65,subduration=1}, -LOW={file="LSO-Low",suffix="ogg",loud=true,subtitle="You're low",duration=0.50,subduration=1}, -POWER={file="LSO-Power",suffix="ogg",loud=true,subtitle="Power",duration=0.50,subduration=1}, -SLOW={file="LSO-Slow",suffix="ogg",loud=true,subtitle="You're slow",duration=0.65,subduration=1}, -FAST={file="LSO-Fast",suffix="ogg",loud=true,subtitle="You're fast",duration=0.70,subduration=1}, -ROGERBALL={file="LSO-RogerBall",suffix="ogg",loud=false,subtitle="Roger ball",duration=1.00,subduration=2}, -WAVEOFF={file="LSO-WaveOff",suffix="ogg",loud=false,subtitle="Wave off",duration=0.6,subduration=5}, -LONGINGROOVE={file="LSO-LongInTheGroove",suffix="ogg",loud=false,subtitle="You're long in the groove",duration=1.2,subduration=5}, -FOULDECK={file="LSO-FoulDeck",suffix="ogg",loud=false,subtitle="Foul deck",duration=0.62,subduration=5}, -DEPARTANDREENTER={file="LSO-DepartAndReenter",suffix="ogg",loud=false,subtitle="Depart and re-enter",duration=1.1,subduration=5}, -PADDLESCONTACT={file="LSO-PaddlesContact",suffix="ogg",loud=false,subtitle="Paddles, contact",duration=1.0,subduration=5}, -WELCOMEABOARD={file="LSO-WelcomeAboard",suffix="ogg",loud=false,subtitle="Welcome aboard",duration=1.0,subduration=5}, -EXPECTHEAVYWAVEOFF={file="LSO-ExpectHeavyWaveoff",suffix="ogg",loud=false,subtitle="Expect heavy waveoff",duration=1.2,subduration=5}, -EXPECTSPOT75={file="LSO-ExpectSpot75",suffix="ogg",loud=false,subtitle="Expect spot 7.5",duration=2.0,subduration=5}, -EXPECTSPOT5={file="LSO-ExpectSpot5",suffix="ogg",loud=false,subtitle="Expect spot 5",duration=1.3,subduration=5}, -STABILIZED={file="LSO-Stabilized",suffix="ogg",loud=false,subtitle="Stabilized",duration=0.9,subduration=5}, -IDLE={file="LSO-Idle",suffix="ogg",loud=false,subtitle="Idle",duration=0.45,subduration=5}, -N0={file="LSO-N0",suffix="ogg",loud=false,subtitle="",duration=0.40}, -N1={file="LSO-N1",suffix="ogg",loud=false,subtitle="",duration=0.25}, -N2={file="LSO-N2",suffix="ogg",loud=false,subtitle="",duration=0.37}, -N3={file="LSO-N3",suffix="ogg",loud=false,subtitle="",duration=0.37}, -N4={file="LSO-N4",suffix="ogg",loud=false,subtitle="",duration=0.39}, -N5={file="LSO-N5",suffix="ogg",loud=false,subtitle="",duration=0.39}, -N6={file="LSO-N6",suffix="ogg",loud=false,subtitle="",duration=0.40}, -N7={file="LSO-N7",suffix="ogg",loud=false,subtitle="",duration=0.40}, -N8={file="LSO-N8",suffix="ogg",loud=false,subtitle="",duration=0.37}, -N9={file="LSO-N9",suffix="ogg",loud=false,subtitle="",duration=0.40}, -CLICK={file="AIRBOSS-RadioClick",suffix="ogg",loud=false,subtitle="",duration=0.35}, -NOISE={file="AIRBOSS-Noise",suffix="ogg",loud=false,subtitle="",duration=3.6}, -SPINIT={file="AIRBOSS-SpinIt",suffix="ogg",loud=false,subtitle="",duration=0.73,subduration=5}, -} -self.PilotCall={ -N0={file="PILOT-N0",suffix="ogg",loud=false,subtitle="",duration=0.40}, -N1={file="PILOT-N1",suffix="ogg",loud=false,subtitle="",duration=0.25}, -N2={file="PILOT-N2",suffix="ogg",loud=false,subtitle="",duration=0.37}, -N3={file="PILOT-N3",suffix="ogg",loud=false,subtitle="",duration=0.37}, -N4={file="PILOT-N4",suffix="ogg",loud=false,subtitle="",duration=0.39}, -N5={file="PILOT-N5",suffix="ogg",loud=false,subtitle="",duration=0.39}, -N6={file="PILOT-N6",suffix="ogg",loud=false,subtitle="",duration=0.40}, -N7={file="PILOT-N7",suffix="ogg",loud=false,subtitle="",duration=0.40}, -N8={file="PILOT-N8",suffix="ogg",loud=false,subtitle="",duration=0.37}, -N9={file="PILOT-N9",suffix="ogg",loud=false,subtitle="",duration=0.40}, -POINT={file="PILOT-Point",suffix="ogg",loud=false,subtitle="",duration=0.33}, -SKYHAWK={file="PILOT-Skyhawk",suffix="ogg",loud=false,subtitle="",duration=0.95,subduration=5}, -HARRIER={file="PILOT-Harrier",suffix="ogg",loud=false,subtitle="",duration=0.58,subduration=5}, -HAWKEYE={file="PILOT-Hawkeye",suffix="ogg",loud=false,subtitle="",duration=0.63,subduration=5}, -TOMCAT={file="PILOT-Tomcat",suffix="ogg",loud=false,subtitle="",duration=0.66,subduration=5}, -HORNET={file="PILOT-Hornet",suffix="ogg",loud=false,subtitle="",duration=0.56,subduration=5}, -VIKING={file="PILOT-Viking",suffix="ogg",loud=false,subtitle="",duration=0.61,subduration=5}, -BALL={file="PILOT-Ball",suffix="ogg",loud=false,subtitle="",duration=0.50,subduration=5}, -BINGOFUEL={file="PILOT-BingoFuel",suffix="ogg",loud=false,subtitle="",duration=0.80}, -GASATDIVERT={file="PILOT-GasAtDivert",suffix="ogg",loud=false,subtitle="",duration=1.80}, -GASATTANKER={file="PILOT-GasAtTanker",suffix="ogg",loud=false,subtitle="",duration=1.95}, -} -self.MarshalCall={ -AFFIRMATIVE={file="MARSHAL-Affirmative",suffix="ogg",loud=false,subtitle="",duration=0.90}, -ALTIMETER={file="MARSHAL-Altimeter",suffix="ogg",loud=false,subtitle="",duration=0.85}, -BRC={file="MARSHAL-BRC",suffix="ogg",loud=false,subtitle="",duration=0.80}, -CARRIERTURNTOHEADING={file="MARSHAL-CarrierTurnToHeading",suffix="ogg",loud=false,subtitle="",duration=2.48,subduration=5}, -CASE={file="MARSHAL-Case",suffix="ogg",loud=false,subtitle="",duration=0.40}, -CHARLIETIME={file="MARSHAL-CharlieTime",suffix="ogg",loud=false,subtitle="",duration=0.90}, -CLEAREDFORRECOVERY={file="MARSHAL-ClearedForRecovery",suffix="ogg",loud=false,subtitle="",duration=1.25}, -DECKCLOSED={file="MARSHAL-DeckClosed",suffix="ogg",loud=false,subtitle="",duration=1.10,subduration=5}, -DEGREES={file="MARSHAL-Degrees",suffix="ogg",loud=false,subtitle="",duration=0.60}, -EXPECTED={file="MARSHAL-Expected",suffix="ogg",loud=false,subtitle="",duration=0.55}, -FLYNEEDLES={file="MARSHAL-FlyYourNeedles",suffix="ogg",loud=false,subtitle="Fly your needles",duration=0.9,subduration=5}, -HOLDATANGELS={file="MARSHAL-HoldAtAngels",suffix="ogg",loud=false,subtitle="",duration=1.10}, -HOURS={file="MARSHAL-Hours",suffix="ogg",loud=false,subtitle="",duration=0.60,subduration=5}, -MARSHALRADIAL={file="MARSHAL-MarshalRadial",suffix="ogg",loud=false,subtitle="",duration=1.10}, -N0={file="MARSHAL-N0",suffix="ogg",loud=false,subtitle="",duration=0.40}, -N1={file="MARSHAL-N1",suffix="ogg",loud=false,subtitle="",duration=0.25}, -N2={file="MARSHAL-N2",suffix="ogg",loud=false,subtitle="",duration=0.37}, -N3={file="MARSHAL-N3",suffix="ogg",loud=false,subtitle="",duration=0.37}, -N4={file="MARSHAL-N4",suffix="ogg",loud=false,subtitle="",duration=0.39}, -N5={file="MARSHAL-N5",suffix="ogg",loud=false,subtitle="",duration=0.39}, -N6={file="MARSHAL-N6",suffix="ogg",loud=false,subtitle="",duration=0.40}, -N7={file="MARSHAL-N7",suffix="ogg",loud=false,subtitle="",duration=0.40}, -N8={file="MARSHAL-N8",suffix="ogg",loud=false,subtitle="",duration=0.37}, -N9={file="MARSHAL-N9",suffix="ogg",loud=false,subtitle="",duration=0.40}, -NEGATIVE={file="MARSHAL-Negative",suffix="ogg",loud=false,subtitle="",duration=0.80,subduration=5}, -NEWFB={file="MARSHAL-NewFB",suffix="ogg",loud=false,subtitle="",duration=1.35}, -OPS={file="MARSHAL-Ops",suffix="ogg",loud=false,subtitle="",duration=0.48}, -POINT={file="MARSHAL-Point",suffix="ogg",loud=false,subtitle="",duration=0.33}, -RADIOCHECK={file="MARSHAL-RadioCheck",suffix="ogg",loud=false,subtitle="Radio check",duration=1.20,subduration=5}, -RECOVERY={file="MARSHAL-Recovery",suffix="ogg",loud=false,subtitle="",duration=0.70,subduration=5}, -RECOVERYOPSSTOPPED={file="MARSHAL-RecoveryOpsStopped",suffix="ogg",loud=false,subtitle="",duration=1.65,subduration=5}, -RECOVERYPAUSEDNOTICE={file="MARSHAL-RecoveryPausedNotice",suffix="ogg",loud=false,subtitle="aircraft recovery paused until further notice",duration=2.90,subduration=5}, -RECOVERYPAUSEDRESUMED={file="MARSHAL-RecoveryPausedResumed",suffix="ogg",loud=false,subtitle="",duration=3.40,subduration=5}, -REPORTSEEME={file="MARSHAL-ReportSeeMe",suffix="ogg",loud=false,subtitle="",duration=0.95}, -RESUMERECOVERY={file="MARSHAL-ResumeRecovery",suffix="ogg",loud=false,subtitle="resuming aircraft recovery",duration=1.75,subduraction=5}, -ROGER={file="MARSHAL-Roger",suffix="ogg",loud=false,subtitle="",duration=0.53,subduration=5}, -SAYNEEDLES={file="MARSHAL-SayNeedles",suffix="ogg",loud=false,subtitle="Say needles",duration=0.90,subduration=5}, -STACKFULL={file="MARSHAL-StackFull",suffix="ogg",loud=false,subtitle="Marshal Stack is currently full. Hold outside 10 NM zone and wait for further instructions",duration=6.35,subduration=10}, -STARTINGRECOVERY={file="MARSHAL-StartingRecovery",suffix="ogg",loud=false,subtitle="",duration=2.65,subduration=5}, -CLICK={file="AIRBOSS-RadioClick",suffix="ogg",loud=false,subtitle="",duration=0.35}, -NOISE={file="AIRBOSS-Noise",suffix="ogg",loud=false,subtitle="",duration=3.6}, -} -self:SetVoiceOversLSOByRaynor() -self:SetVoiceOversMarshalByRaynor() -end -function AIRBOSS:SetVoiceOver(radiocall,duration,subtitle,subduration,filename,suffix) -radiocall.duration=duration -radiocall.subtitle=subtitle or radiocall.subtitle -radiocall.file=filename -radiocall.suffix=suffix or".ogg" -end -function AIRBOSS:_GetAircraftAoA(playerData) -local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET -or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE -or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF -or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER -local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C -local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC -local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B -local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B -local aoa={} -if hornet then -aoa.SLOW=9.8 -aoa.Slow=9.3 -aoa.OnSpeedMax=8.8 -aoa.OnSpeed=8.1 -aoa.OnSpeedMin=7.4 -aoa.Fast=6.9 -aoa.FAST=6.3 -elseif tomcat then -aoa.SLOW=self:_AoAUnit2Deg(playerData,17.0) -aoa.Slow=self:_AoAUnit2Deg(playerData,16.0) -aoa.OnSpeedMax=self:_AoAUnit2Deg(playerData,15.5) -aoa.OnSpeed=self:_AoAUnit2Deg(playerData,15.0) -aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.5) -aoa.Fast=self:_AoAUnit2Deg(playerData,14.0) -aoa.FAST=self:_AoAUnit2Deg(playerData,13.0) -elseif goshawk then -aoa.SLOW=8.00 -aoa.Slow=7.75 -aoa.OnSpeedMax=7.25 -aoa.OnSpeed=7.00 -aoa.OnSpeedMin=6.75 -aoa.Fast=6.25 -aoa.FAST=6.00 -elseif skyhawk then -aoa.SLOW=9.50 -aoa.Slow=9.25 -aoa.OnSpeedMax=9.00 -aoa.OnSpeed=8.75 -aoa.OnSpeedMin=8.50 -aoa.Fast=8.25 -aoa.FAST=8.00 -elseif harrier then -aoa.SLOW=16.0 -aoa.Slow=13.5 -aoa.OnSpeedMax=12.5 -aoa.OnSpeed=10.0 -aoa.OnSpeedMin=9.5 -aoa.Fast=8.0 -aoa.FAST=7.5 -end -return aoa -end -function AIRBOSS:_AoAUnit2Deg(playerData,aoaunits) -local degrees=aoaunits -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then -degrees=-10+50/30*aoaunits -degrees=0.918*aoaunits-3.411 -elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then -degrees=0.5*aoaunits -end -return degrees -end -function AIRBOSS:_AoADeg2Units(playerData,degrees) -local aoaunits=degrees -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then -aoaunits=(degrees+10)*30/50 -aoaunits=1.089*degrees+3.715 -elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then -aoaunits=2*degrees -end -return aoaunits -end -function AIRBOSS:_GetAircraftParameters(playerData,step) -step=step or playerData.step -local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET -or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE -or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF -or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER -local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC -local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B -local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B -local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C -local alt -local aoa -local dist -local speed -local aoaac=self:_GetAircraftAoA(playerData) -if step==AIRBOSS.PatternStep.PLATFORM then -alt=UTILS.FeetToMeters(5000) -speed=UTILS.KnotsToMps(250) -elseif step==AIRBOSS.PatternStep.ARCIN then -if tomcat then -speed=UTILS.KnotsToMps(150) -else -speed=UTILS.KnotsToMps(250) -end -elseif step==AIRBOSS.PatternStep.ARCOUT then -if tomcat then -speed=UTILS.KnotsToMps(150) -else -speed=UTILS.KnotsToMps(250) -end -elseif step==AIRBOSS.PatternStep.DIRTYUP then -alt=UTILS.FeetToMeters(1200) -elseif step==AIRBOSS.PatternStep.BULLSEYE then -alt=UTILS.FeetToMeters(1200) -dist=-UTILS.NMToMeters(3) -aoa=aoaac.OnSpeed -elseif step==AIRBOSS.PatternStep.INITIAL then -if hornet or tomcat or harrier then -alt=UTILS.FeetToMeters(800) -speed=UTILS.KnotsToMps(350) -elseif skyhawk then -alt=UTILS.FeetToMeters(600) -speed=UTILS.KnotsToMps(250) -elseif goshawk then -alt=UTILS.FeetToMeters(800) -speed=UTILS.KnotsToMps(300) -end -elseif step==AIRBOSS.PatternStep.BREAKENTRY then -if hornet or tomcat or harrier then -alt=UTILS.FeetToMeters(800) -speed=UTILS.KnotsToMps(350) -elseif skyhawk then -alt=UTILS.FeetToMeters(600) -speed=UTILS.KnotsToMps(250) -elseif goshawk then -alt=UTILS.FeetToMeters(800) -speed=UTILS.KnotsToMps(300) -end -elseif step==AIRBOSS.PatternStep.EARLYBREAK then -if hornet or tomcat or harrier or goshawk then -alt=UTILS.FeetToMeters(800) -elseif skyhawk then -alt=UTILS.FeetToMeters(600) -end -elseif step==AIRBOSS.PatternStep.LATEBREAK then -if hornet or tomcat or harrier or goshawk then -alt=UTILS.FeetToMeters(800) -elseif skyhawk then -alt=UTILS.FeetToMeters(600) -end -elseif step==AIRBOSS.PatternStep.ABEAM then -if hornet or tomcat or harrier or goshawk then -alt=UTILS.FeetToMeters(600) -elseif skyhawk then -alt=UTILS.FeetToMeters(500) -end -aoa=aoaac.OnSpeed -if goshawk then -dist=UTILS.NMToMeters(0.9) -elseif harrier then -dist=UTILS.NMToMeters(0.9) -else -dist=UTILS.NMToMeters(1.1) -end -elseif step==AIRBOSS.PatternStep.NINETY then -if hornet or tomcat then -alt=UTILS.FeetToMeters(500) -elseif goshawk then -alt=UTILS.FeetToMeters(450) -elseif skyhawk then -alt=UTILS.FeetToMeters(500) -elseif harrier then -alt=UTILS.FeetToMeters(425) -end -aoa=aoaac.OnSpeed -elseif step==AIRBOSS.PatternStep.WAKE then -if hornet or goshawk then -alt=UTILS.FeetToMeters(370) -elseif tomcat then -alt=UTILS.FeetToMeters(430) -elseif skyhawk then -alt=UTILS.FeetToMeters(370) -end -aoa=aoaac.OnSpeed -elseif step==AIRBOSS.PatternStep.FINAL then -if hornet or goshawk then -alt=UTILS.FeetToMeters(300) -elseif tomcat then -alt=UTILS.FeetToMeters(360) -elseif skyhawk then -alt=UTILS.FeetToMeters(300) -elseif harrier then -alt=UTILS.FeetToMeters(312) -end -aoa=aoaac.OnSpeed -end -return alt,aoa,dist,speed -end -function AIRBOSS:_GetNextMarshalFight() -for _,_flight in pairs(self.Qmarshal)do -local flight=_flight -local stack=flight.flag -local Tmarshal=timer.getAbsTime()-flight.time -local TmarshalMin=2*60 -if flight.ai then -TmarshalMin=3*60 -end -if flight.holding~=nil and Tmarshal>=TmarshalMin then -if flight.case==1 and stack==1 or flight.case>1 then -if flight.ai then -return flight -else -if flight.step~=AIRBOSS.PatternStep.COMMENCING then -return flight -end -end -end -end -end -return nil -end -function AIRBOSS:_CheckQueue() -if self.Debug then -self:_PrintQueue(self.flights,"All Flights") -end -self:_PrintQueue(self.Qmarshal,"Marshal") -self:_PrintQueue(self.Qpattern,"Pattern") -self:_PrintQueue(self.Qwaiting,"Waiting") -self:_PrintQueue(self.Qspinning,"Spinning") -if self.case>1 then -for _,_flight in pairs(self.Qwaiting)do -local flight=_flight -local removed=self:_RemoveFlightFromQueue(self.Qwaiting,flight) -if removed then -local stack=self:_GetFreeStack(flight.ai) -self:T(self.lid..string.format("Moving flight %s onboard %s from Waiting queue to Case %d Marshal stack %d",flight.groupname,flight.onboard,self.case,stack)) -if flight.ai then -self:_MarshalAI(flight,stack) -else -self:_MarshalPlayer(flight,stack) -end -break -end -end -end -if not self:IsRecovering()then -for _,_flight in pairs(self.Qmarshal)do -local flight=_flight -if(flight.case==1 and self.case>1)or(flight.case>1 and self.case==1)then -local removed=self:_RemoveFlightFromQueue(self.Qmarshal,flight) -if removed then -local stack=self:_GetFreeStack(flight.ai) -self:T(self.lid..string.format("Moving flight %s onboard %s from Marshal Case %d ==> %d Marshal stack %d",flight.groupname,flight.onboard,flight.case,self.case,stack)) -if flight.ai then -self:_MarshalAI(flight,stack) -else -self:_MarshalPlayer(flight,stack) -end -break -elseif flight.case~=self.case then -flight.case=self.case -end -end -end -return -end -local _,npattern=self:_GetQueueInfo(self.Qpattern) -local _,nspinning=self:_GetQueueInfo(self.Qspinning) -local marshalflight=self:_GetNextMarshalFight() -if marshalflight and npattern0 then -local patternflight=self.Qpattern[#self.Qpattern] -pcase=patternflight.case -local npunits=self:_GetFlightUnits(patternflight,false) -Tpattern=timer.getAbsTime()-patternflight.time -self:T(self.lid..string.format("Pattern time of last group %s = %d seconds. # of units=%d.",patternflight.groupname,Tpattern,npunits)) -end -local TpatternMin -if pcase==1 then -TpatternMin=2*60*npunits -else -TpatternMin=2*60*npunits -end -if Tpattern>TpatternMin then -self:T(self.lid..string.format("Sending marshal flight %s to pattern.",marshalflight.groupname)) -self:_ClearForLanding(marshalflight) -end -end -end -function AIRBOSS:_ClearForLanding(flight) -if flight.ai then -self:_RemoveFlightFromMarshalQueue(flight,false) -self:_LandAI(flight) -self:_MarshalCallClearedForRecovery(flight.onboard,flight.case) -if self.xtVoiceOversAI then -local leader=flight.group:GetUnits()[1] -self:_CommencingCall(leader,flight.onboard) -end -else -if flight.step~=AIRBOSS.PatternStep.COMMENCING then -self:_MarshalCallClearedForRecovery(flight.onboard,flight.case) -flight.time=timer.getAbsTime() -end -self:_SetPlayerStep(flight,AIRBOSS.PatternStep.COMMENCING,3) -end -end -function AIRBOSS:_SetPlayerStep(playerData,step,delay) -if delay and delay>0 then -self:ScheduleOnce(delay,self._SetPlayerStep,self,playerData,step) -else -if playerData then -playerData.step=step -playerData.warning=nil -self:_StepHint(playerData) -end -end -end -function AIRBOSS:_ScanCarrierZone() -local coord=self:GetCoordinate() -local RCCZ=self.zoneCCA:GetRadius() -self:T(self.lid..string.format("Scanning Carrier Controlled Area. Radius=%.1f NM.",UTILS.MetersToNM(RCCZ))) -local _,_,_,unitscan=coord:ScanObjects(RCCZ,true,false,false) -local insideCCA={} -for _,_unit in pairs(unitscan)do -local unit=_unit -local airborne=unit:IsAir() -local inzone=unit:IsInZone(self.zoneCCA) -local friendly=self:GetCoalition()==unit:GetCoalition() -local carrierac=self:_IsCarrierAircraft(unit) -if airborne and inzone and friendly and carrierac then -local group=unit:GetGroup() -local groupname=group:GetName() -if insideCCA[groupname]==nil then -insideCCA[groupname]=group -end -end -end -for groupname,_group in pairs(insideCCA)do -local group=_group -local knownflight=self:_GetFlightFromGroupInQueue(group,self.flights) -local actype=group:GetTypeName() -if knownflight then -self:T2(self.lid..string.format("Known flight group %s of type %s in CCA.",groupname,actype)) -if knownflight.ai and knownflight.flag==-100 and self.handleai then -local putintomarshal=false -local flight=_DATABASE:GetOpsGroup(groupname) -if flight and flight:IsInbound()and flight.destbase:GetName()==self.carrier:GetName()then -if flight.ishelo then -else -putintomarshal=true -end -flight.airboss=self -end -if putintomarshal then -local stack=self:_GetFreeStack(knownflight.ai) -local respawn=self.respawnAI -if stack then -self:_MarshalAI(knownflight,stack,respawn) -else -if not self:_InQueue(self.Qwaiting,knownflight.group)then -self:_WaitAI(knownflight,respawn) -end -end -break -end -end -else -if not self:_IsHuman(group)then -self:_CreateFlightGroup(group) -end -end -end -local remove={} -for _,_flight in pairs(self.flights)do -local flight=_flight -if insideCCA[flight.groupname]==nil then -if flight.ai and not(self:_InQueue(self.Qmarshal,flight.group)or self:_InQueue(self.Qpattern,flight.group))then -table.insert(remove,flight) -end -end -end -for _,flight in pairs(remove)do -self:_RemoveFlightFromQueue(self.flights,flight) -end -end -function AIRBOSS:_WaitPlayer(playerData) -if playerData then -local nwaiting=#self.Qwaiting -self:_MarshalCallStackFull(playerData.onboard,nwaiting) -table.insert(self.Qwaiting,playerData) -playerData.time=timer.getAbsTime() -playerData.step=AIRBOSS.PatternStep.WAITING -playerData.warning=nil -for _,_flight in pairs(playerData.section)do -local flight=_flight -flight.step=AIRBOSS.PatternStep.WAITING -flight.time=timer.getAbsTime() -flight.warning=nil -end -end -end -function AIRBOSS:_MarshalPlayer(playerData,stack) -if playerData then -self:_AddMarshalGroup(playerData,stack) -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.HOLDING) -playerData.holding=nil -for _,_flight in pairs(playerData.section)do -local flight=_flight -self:_SetPlayerStep(flight,AIRBOSS.PatternStep.HOLDING) -flight.holding=nil -flight.case=playerData.case -flight.flag=stack -self:Marshal(flight) -end -else -self:E(self.lid.."ERROR: Could not add player to Marshal stack! playerData=nil") -end -end -function AIRBOSS:_WaitAI(flight,respawn) -flight.flag=-99 -table.insert(self.Qwaiting,flight) -local group=flight.group -local groupname=flight.groupname -local speedOrbitMps=UTILS.KnotsToMps(274) -local speedOrbitKmh=UTILS.KnotsToKmph(274) -local speedTransit=UTILS.KnotsToKmph(370) -local cv=self:GetCoordinate() -local fc=group:GetCoordinate() -local hdg=self:GetHeading(false) -local hdgto=cv:HeadingTo(fc) -local angels=math.random(6,10) -local altitude=UTILS.FeetToMeters(angels*1000) -local p0=cv:Translate(UTILS.NMToMeters(11),hdgto):Translate(UTILS.NMToMeters(5),hdg):SetAltitude(altitude) -local wp={} -wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil,speedTransit,{},"Current Position") -local taskorbit=group:TaskOrbit(p0,altitude,speedOrbitMps) -wp[#wp+1]=p0:WaypointAirTurningPoint(nil,speedOrbitKmh,{taskorbit},string.format("Waiting Orbit at Angels %d",angels)) -if self.Debug then -p0:MarkToAll(string.format("Waiting Orbit of flight %s at Angels %s",groupname,angels)) -end -if respawn then -local Template=group:GetTemplate() -Template.route.points=wp -group=group:Respawn(Template,true) -end -group:WayPointInitialize(wp) -group:Route(wp,1) -end -function AIRBOSS:_MarshalAI(flight,nstack,respawn) -self:F2({flight=flight,nstack=nstack,respawn=respawn}) -if flight==nil or flight.group==nil then -self:E(self.lid.."ERROR: flight or flight.group is nil.") -return -end -if flight.group:GetCoordinate()==nil then -self:E(self.lid.."ERROR: cannot get coordinate of flight group.") -return -end -if not self:_InQueue(self.Qmarshal,flight.group)then -if self.xtVoiceOversAI then -local leader=flight.group:GetUnits()[1] -self:_MarshallInboundCall(leader,flight.onboard) -end -self:_AddMarshalGroup(flight,nstack) -end -local case=flight.case -local ostack=flight.flag -local group=flight.group -local groupname=flight.groupname -flight.flag=nstack -local Carrier=self:GetCoordinate() -local hdg=self:GetHeading() -local speedOrbitMps=UTILS.KnotsToMps(274) -local speedOrbitKmh=UTILS.KnotsToKmph(274) -local speedTransit=UTILS.KnotsToKmph(370) -local altitude -local p0 -local p1 -local p2 -altitude,p1,p2=self:_GetMarshalAltitude(nstack,case) -local wp={} -if not flight.holding then -self:T(self.lid..string.format("Guiding AI flight %s to marshal stack %d-->%d.",groupname,ostack,nstack)) -wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil,speedTransit,{},"Current Position") -local TaskArrivedHolding=flight.group:TaskFunction("AIRBOSS._ReachedHoldingZone",self,flight) -if case==1 then -local pE=Carrier:Translate(UTILS.NMToMeters(7),hdg-30):SetAltitude(altitude) -p0=Carrier:Translate(UTILS.NMToMeters(5),hdg-135):SetAltitude(altitude) -wp[#wp+1]=pE:WaypointAirTurningPoint(nil,speedTransit,{TaskArrivedHolding},"Entering Case I Marshal Pattern") -else -local radial=self:GetRadial(case,false,true) -p0=p2:Translate(UTILS.NMToMeters(5),radial+90,true):Translate(UTILS.NMToMeters(5),radial,true) -wp[#wp+1]=p0:WaypointAirTurningPoint(nil,speedTransit,{TaskArrivedHolding},"Entering Case II/III Marshal Pattern") -end -else -self:T(self.lid..string.format("Updating AI flight %s at marshal stack %d-->%d.",groupname,ostack,nstack)) -wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil,speedOrbitKmh,{},"Current Position") -p0=group:GetCoordinate():Translate(UTILS.NMToMeters(0.2),group:GetHeading(),true) -end -local taskorbit=group:TaskOrbit(p1,altitude,speedOrbitMps,p2) -wp[#wp+1]=p0:WaypointAirTurningPoint(nil,speedOrbitKmh,{taskorbit},string.format("Marshal Orbit Stack %d",nstack)) -if self.Debug then -p0:MarkToAll("WP P0 "..groupname) -p1:MarkToAll("RT P1 "..groupname) -p2:MarkToAll("RT P2 "..groupname) -end -if respawn then -local Template=group:GetTemplate() -Template.route.points=wp -flight.group=group:Respawn(Template,true) -end -flight.group:WayPointInitialize(wp) -flight.group:Route(wp,1) -self:Marshal(flight) -end -function AIRBOSS:_RefuelAI(flight) -local wp={} -local CurrentSpeed=flight.group:GetVelocityKMH() -wp[#wp+1]=flight.group:GetCoordinate():WaypointAirTurningPoint(nil,CurrentSpeed,{},"Current position") -local refuelac=false -local actype=flight.group:GetTypeName() -if actype==AIRBOSS.AircraftCarrier.AV8B or -actype==AIRBOSS.AircraftCarrier.F14A or -actype==AIRBOSS.AircraftCarrier.F14B or -actype==AIRBOSS.AircraftCarrier.F14A_AI or -actype==AIRBOSS.AircraftCarrier.HORNET or -actype==AIRBOSS.AircraftCarrier.RHINOE or -actype==AIRBOSS.AircraftCarrier.RHINOF or -actype==AIRBOSS.AircraftCarrier.GROWLER or -actype==AIRBOSS.AircraftCarrier.FA18C or -actype==AIRBOSS.AircraftCarrier.S3B or -actype==AIRBOSS.AircraftCarrier.S3BTANKER then -refuelac=true -end -local text="" -if self.tanker and refuelac then -local tankerpos=self.tanker.tanker:GetCoordinate() -local TaskRefuel=flight.group:TaskRefueling() -local TaskMarshal=flight.group:TaskFunction("AIRBOSS._TaskFunctionMarshalAI",self,flight) -wp[#wp+1]=tankerpos:WaypointAirTurningPoint(nil,CurrentSpeed,{TaskRefuel,TaskMarshal},"Refueling") -self:_MarshalCallGasAtTanker(flight.onboard) -else -local divertfield=self:GetCoordinate():GetClosestAirbase(Airbase.Category.AIRDROME,self:GetCoalition()) -if divertfield==nil then -divertfield=self:GetCoordinate():GetClosestAirbase(Airbase.Category.AIRDROME,0) -end -if divertfield then -local divertcoord=divertfield:GetCoordinate() -wp[#wp+1]=divertcoord:WaypointAirLanding(UTILS.KnotsToKmph(300),divertfield,{},"Divert Field") -self:_MarshalCallGasAtDivert(flight.onboard,divertfield:GetName()) -local Template=flight.group:GetTemplate() -Template.route.points=wp -flight.group=flight.group:Respawn(Template,true) -else -self:E(self.lid..string.format("WARNING: No recovery tanker or divert field available for group %s.",flight.groupname)) -flight.refueling=true -return -end -end -flight.group:WayPointInitialize(wp) -flight.group:Route(wp,1) -flight.refueling=true -end -function AIRBOSS:_LandAI(flight) -self:T(self.lid..string.format("Landing AI flight %s.",flight.groupname)) -local Speed=UTILS.KnotsToKmph(200) -if flight.actype==AIRBOSS.AircraftCarrier.HORNET -or flight.actype==AIRBOSS.AircraftCarrier.FA18C -or flight.actype==AIRBOSS.AircraftCarrier.RHINOE -or flight.actype==AIRBOSS.AircraftCarrier.RHINOF -or flight.actype==AIRBOSS.AircraftCarrier.GROWLER then -Speed=UTILS.KnotsToKmph(200) -elseif flight.actype==AIRBOSS.AircraftCarrier.E2D then -Speed=UTILS.KnotsToKmph(150) -elseif flight.actype==AIRBOSS.AircraftCarrier.F14A_AI or flight.actype==AIRBOSS.AircraftCarrier.F14A or flight.actype==AIRBOSS.AircraftCarrier.F14B then -Speed=UTILS.KnotsToKmph(175) -elseif flight.actype==AIRBOSS.AircraftCarrier.S3B or flight.actype==AIRBOSS.AircraftCarrier.S3BTANKER then -Speed=UTILS.KnotsToKmph(140) -end -local Carrier=self:GetCoordinate() -local hdg=self:GetHeading() -local wp={} -local CurrentSpeed=flight.group:GetVelocityKMH() -wp[#wp+1]=flight.group:GetCoordinate():WaypointAirTurningPoint(nil,CurrentSpeed,{},"Current position") -local alt=UTILS.FeetToMeters(800) -wp[#wp+1]=Carrier:Translate(UTILS.NMToMeters(4),hdg-160):SetAltitude(alt):WaypointAirLanding(Speed,self.airbase,nil,"Landing") -flight.group:WayPointInitialize(wp) -flight.group:Route(wp,0) -end -function AIRBOSS:_GetMarshalAltitude(stack,case) -if stack<=0 then -return 0,nil,nil -end -case=case or self.case -local Carrier=self:GetCoordinate() -local angels0 -local Dist -local p1=nil -local p2=nil -local nstack=stack-1 -if case==1 then -angels0=2 -local hdg=self.carrier:GetHeading() -p1=Carrier -p2=Carrier:Translate(UTILS.NMToMeters(1.5),hdg) -if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -p1=Carrier:Translate(UTILS.NMToMeters(1.0),hdg+90) -p2=p1:Translate(2.5,hdg) -end -else -angels0=6 -Dist=UTILS.NMToMeters(nstack+angels0+15) -local radial=self:GetRadial(case,false,true) -local l=UTILS.NMToMeters(10) -p1=Carrier:Translate(Dist+l,radial) -p2=Carrier:Translate(Dist,radial) -end -local altitude=UTILS.FeetToMeters((nstack+angels0)*1000) -p1:SetAltitude(altitude,true) -p2:SetAltitude(altitude,true) -return altitude,p1,p2 -end -function AIRBOSS:_GetCharlieTime(flightgroup) -local stack=flightgroup.flag -if stack<=0 then -return nil -end -local Tnow=timer.getAbsTime() -local Tcharlie=0 -local Trecovery=0 -if self.recoverywindow then -Trecovery=math.max(self.recoverywindow.START-Tnow,0) -else -Trecovery=7*60 -end -for _,_flight in pairs(self.Qmarshal)do -local flight=_flight -local mstack=flight.flag -local Tarrive=0 -local Tholding=3*60 -if stack>0 and mstack>0 and mstack<=stack then -if flight.holding==nil then -local holdingzone=self:_GetZoneHolding(flight.case,1):GetCoordinate() -local d0=holdingzone:Get2DDistance(flight.group:GetCoordinate()) -local v0=flight.group:GetVelocityMPS() -Tarrive=d0/v0 -self:T3(self.lid..string.format("Tarrive=%.1f seconds, Clock %s",Tarrive,UTILS.SecondsToClock(Tnow+Tarrive))) -else -if mstack==1 then -local tholding=timer.getAbsTime()-flight.time -Tholding=math.max(3*60-tholding,0) -end -end -local Tmin=math.max(Tarrive,Trecovery) -Tcharlie=math.max(Tmin,Tcharlie)+Tholding -end -end -Tcharlie=Tcharlie+Tnow -local text=string.format("Charlie time for flight %s (%s) %s",flightgroup.onboard,flightgroup.groupname,UTILS.SecondsToClock(Tcharlie)) -MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) -self:T(self.lid..text) -return Tcharlie -end -function AIRBOSS:_AddMarshalGroup(flight,stack) -flight.flag=stack -flight.case=self.case -table.insert(self.Qmarshal,flight) -local P=UTILS.hPa2inHg(self:GetCoordinate():GetPressure()) -local alt=self:_GetMarshalAltitude(stack,flight.case) -local brc=self:GetBRC() -if self.recoverywindow and self.recoverywindow.WIND then -brc=self:GetBRCintoWind(self.recoverywindow.SPEED) -end -flight.Tcharlie=self:_GetCharlieTime(flight) -local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) -self:_MarshalCallArrived(flight.onboard,flight.case,brc,alt,Ccharlie,P) -if self.TACANon and(not flight.ai)and flight.difficulty==AIRBOSS.Difficulty.EASY then -local radial=self:GetRadial(flight.case,true,true,true) -if flight.case==1 then -radial=self:GetBRC() -end -local text=string.format("Select TACAN %03d°, channel %d%s (%s)",radial,self.TACANchannel,self.TACANmode,self.TACANmorse) -self:MessageToPlayer(flight,text,nil,"") -end -end -function AIRBOSS:_CollapseMarshalStack(flight,nopattern) -self:F2({flight=flight,nopattern=nopattern}) -local case=flight.case -local stack=flight.flag -if stack<=0 then -self:E(self.lid..string.format("ERROR: Flight %s is has stack value %d<0. Cannot collapse stack!",flight.groupname,stack)) -return -end -self.Tcollapse=timer.getTime() -for _,_flight in pairs(self.Qmarshal)do -local mflight=_flight -if(case==1 and mflight.case==1)then -local mstack=mflight.flag -if mstack>stack then -local newstack=self:_GetFreeStack(mflight.ai,mflight.case,true) -if newstack and newstack %d.",mflight.groupname,mflight.case,mstack,newstack)) -if mflight.ai then -self:_MarshalAI(mflight,newstack) -else -mflight.flag=newstack -local angels=self:_GetAngels(self:_GetMarshalAltitude(newstack,case)) -if mflight.difficulty~=AIRBOSS.Difficulty.HARD then -local text=string.format("descent to stack at Angels %d.",angels) -self:MessageToPlayer(mflight,text,"MARSHAL") -end -mflight.time=timer.getAbsTime() -for _,_sec in pairs(mflight.section)do -local sec=_sec -sec.flag=newstack -sec.time=timer.getAbsTime() -if sec.difficulty~=AIRBOSS.Difficulty.HARD then -local text=string.format("descent to stack at Angels %d.",angels) -self:MessageToPlayer(sec,text,"MARSHAL") -end -end -end -end -end -end -end -if nopattern then -self:T(self.lid..string.format("Flight %s is leaving stack but not going to pattern.",flight.groupname)) -else -local Tmarshal=UTILS.SecondsToClock(timer.getAbsTime()-flight.time) -self:T(self.lid..string.format("Flight %s is leaving marshal after %s and going pattern.",flight.groupname,Tmarshal)) -self:_AddFlightToPatternQueue(flight) -end -flight.flag=-1 -flight.time=timer.getAbsTime() -end -function AIRBOSS:_GetFreeStack(ai,case,empty) -case=case or self.case -if case==1 then -return self:_GetFreeStack_Old(ai,case,empty) -end -local nmaxstacks=100 -if case==1 then -nmaxstacks=self.Nmaxmarshal -end -local stack={} -for i=1,nmaxstacks do -stack[i]=self.NmaxStack -end -local nmax=1 -for _,_flight in pairs(self.Qmarshal)do -local flight=_flight -if flight.case==case then -local n=flight.flag -if n>nmax then -nmax=n -end -if n>0 then -if flight.ai or flight.case>1 then -stack[n]=0 -else -stack[n]=stack[n]-1 -end -else -self:E(string.format("ERROR: Flight %s in marshal stack has stack value <= 0. Stack value is %d.",flight.groupname,n)) -end -end -end -local nfree=nil -if stack[nmax]==0 then -if case==1 then -if nmax>=nmaxstacks then -nfree=nil -else -nfree=nmax+1 -end -else -nfree=nmax+1 -end -elseif stack[nmax]==self.NmaxStack then -self:E(self.lid..string.format("ERROR: Max occupied stack is empty. Should not happen! Nmax=%d, stack[nmax]=%d",nmax,stack[nmax])) -nfree=nmax -else -if ai or empty or case>1 then -nfree=nmax+1 -else -nfree=nmax -end -end -self:T(self.lid..string.format("Returning free stack %s",tostring(nfree))) -return nfree -end -function AIRBOSS:_GetFreeStack_Old(ai,case,empty) -case=case or self.case -local nmaxstacks=100 -if case==1 then -nmaxstacks=self.Nmaxmarshal -end -local stack={} -for i=1,nmaxstacks do -stack[i]=self.NmaxStack -end -for _,_flight in pairs(self.Qmarshal)do -local flight=_flight -if flight.case==case then -local n=flight.flag -if n>0 then -if flight.ai or flight.case>1 then -stack[n]=0 -else -stack[n]=stack[n]-1 -end -else -self:E(string.format("ERROR: Flight %s in marshal stack has stack value <= 0. Stack value is %d.",flight.groupname,n)) -end -end -end -local nfree=nil -for i=1,nmaxstacks do -self:T2(self.lid..string.format("FF Stack[%d]=%d",i,stack[i])) -if ai or empty or case>1 then -if stack[i]==self.NmaxStack then -nfree=i -return i -end -else -if stack[i]>0 then -nfree=i -return i -end -end -end -return nfree -end -function AIRBOSS:_GetFlightUnits(flight,onground) -local inair=true -if onground==true then -inair=false -end -local function countunits(_group,inair) -local group=_group -local units=group:GetUnits() -local n=0 -if units then -for _,_unit in pairs(units)do -local unit=_unit -if unit and unit:IsAlive()then -if inair then -if unit:InAir()then -self:T2(self.lid..string.format("Unit %s is in AIR",unit:GetName())) -n=n+1 -end -else -n=n+1 -end -end -end -end -return n -end -local nunits=countunits(flight.group,inair) -local nsection=0 -for _,sec in pairs(flight.section)do -local secflight=sec -nsection=nsection+countunits(secflight.group,inair) -end -return nunits+nsection,nunits,nsection -end -function AIRBOSS:_GetQueueInfo(queue,case) -local ngroup=0 -local Nunits=0 -for _,_flight in pairs(queue)do -local flight=_flight -if case then -if(flight.case==case)or(case==23 and(flight.case==2 or flight.case==3))then -local ntot,nunits,nsection=self:_GetFlightUnits(flight) -Nunits=Nunits+ntot -if ntot>0 then -ngroup=ngroup+1 -end -end -else -local ntot,nunits,nsection=self:_GetFlightUnits(flight) -Nunits=Nunits+ntot -if ntot>0 then -ngroup=ngroup+1 -end -end -end -return ngroup,Nunits -end -function AIRBOSS:_PrintQueue(queue,name) -local Nqueue,nqueue=self:_GetQueueInfo(queue) -local text=string.format("%s Queue N=%d (#%d), n=%d:",name,Nqueue,#queue,nqueue) -if#queue==0 then -text=text.." empty." -else -for i,_flight in pairs(queue)do -local flight=_flight -local clock=UTILS.SecondsToClock(timer.getAbsTime()-flight.time) -local case=flight.case -local stack=flight.flag -local fuel=flight.group:GetFuelMin()*100 -local ai=tostring(flight.ai) -local lead=flight.seclead -local Nsec=#flight.section -local actype=self:_GetACNickname(flight.actype) -local onboard=flight.onboard -local holding=tostring(flight.holding) -local _,nunits,nsec=self:_GetFlightUnits(flight,false) -text=text..string.format("\n[%d] %s*%d (%s): lead=%s (%d/%d), onboard=%s, flag=%d, case=%d, time=%s, fuel=%d, ai=%s, holding=%s",i,flight.groupname,nunits,actype,lead,nsec,Nsec,onboard,stack,case,clock,fuel,ai,holding) -if stack>0 then -local alt=UTILS.MetersToFeet(self:_GetMarshalAltitude(stack,case)) -text=text..string.format(" stackalt=%d ft",alt) -end -for j,_element in pairs(flight.elements)do -local element=_element -text=text..string.format("\n (%d) %s (%s): ai=%s, ballcall=%s, recovered=%s",j,element.onboard,element.unitname,tostring(element.ai),tostring(element.ballcall),tostring(element.recovered)) -end -end -end -self:T(self.lid..text) -end -function AIRBOSS:_CreateFlightGroup(group) -self:T(self.lid..string.format("Creating new flight for group %s of aircraft type %s.",group:GetName(),group:GetTypeName())) -local flight={} -if not self:_InQueue(self.flights,group)then -local groupname=group:GetName() -local human,playername=self:_IsHuman(group) -flight.group=group -flight.groupname=group:GetName() -flight.nunits=#group:GetUnits() -flight.time=timer.getAbsTime() -flight.dist0=group:GetCoordinate():Get2DDistance(self:GetCoordinate()) -flight.flag=-100 -flight.ai=not human -flight.actype=group:GetTypeName() -flight.onboardnumbers=self:_GetOnboardNumbers(group) -flight.seclead=flight.group:GetUnit(1):GetName() -flight.section={} -flight.ballcall=false -flight.refueling=false -flight.holding=nil -flight.name=flight.group:GetUnit(1):GetName() -flight.case=self.case -local text=string.format("Flight elements of group %s:",flight.groupname) -flight.elements={} -local units=group:GetUnits() -for i,_unit in pairs(units)do -local unit=_unit -local element={} -element.unit=unit -element.unitname=unit:GetName() -element.onboard=flight.onboardnumbers[element.unitname] -element.ballcall=false -element.ai=not self:_IsHumanUnit(unit) -element.recovered=nil -text=text..string.format("\n[%d] %s onboard #%s, AI=%s",i,element.unitname,tostring(element.onboard),tostring(element.ai)) -table.insert(flight.elements,element) -end -self:T(self.lid..text) -if flight.ai then -local onboard=flight.onboardnumbers[flight.seclead] -flight.onboard=onboard -else -flight.onboard=self:_GetOnboardNumberPlayer(group) -end -table.insert(self.flights,flight) -else -self:E(self.lid..string.format("ERROR: Flight group %s already exists in self.flights!",group:GetName())) -return nil -end -return flight -end -function AIRBOSS:_NewPlayer(unitname) -local playerunit,playername=self:_GetPlayerUnitAndName(unitname) -if playerunit and playername then -local group=playerunit:GetGroup() -local playerData -playerData=self:_CreateFlightGroup(group) -if playerData then -playerData.unit=playerunit -playerData.unitname=unitname -playerData.name=playername -playerData.callsign=playerData.unit:GetCallsign() -playerData.client=CLIENT:FindByName(unitname,nil,true) -playerData.seclead=playername -playerData.passes=0 -playerData.messages={} -playerData.lastdebrief=playerData.lastdebrief or{} -playerData.attitudemonitor=false -if playerData.trapon==nil then -playerData.trapon=self.trapsheet -end -playerData.difficulty=playerData.difficulty or self.defaultskill -if playerData.subtitles==nil then -playerData.subtitles=true -end -if playerData.showhints==nil then -if playerData.difficulty==AIRBOSS.Difficulty.HARD then -playerData.showhints=false -else -playerData.showhints=true -end -end -playerData.points={} -playerData=self:_InitPlayer(playerData) -self.players[playername]=playerData -self.playerscores[playername]=self.playerscores[playername]or{} -if self.welcome then -self:MessageToPlayer(playerData,string.format("Welcome, %s %s!",playerData.difficulty,playerData.name),string.format("AIRBOSS %s",self.alias),"",5) -end -end -return playerData -end -return nil -end -function AIRBOSS:_InitPlayer(playerData,step) -self:T(self.lid..string.format("Initializing player data for %s callsign %s.",playerData.name,playerData.callsign)) -playerData.step=step or AIRBOSS.PatternStep.UNDEFINED -playerData.groove={} -playerData.debrief={} -playerData.trapsheet={} -playerData.warning=nil -playerData.holding=nil -playerData.refueling=false -playerData.valid=false -playerData.lig=false -playerData.wop=false -playerData.waveoff=false -playerData.wofd=false -playerData.owo=false -playerData.boltered=false -playerData.hover=false -playerData.stable=false -playerData.landed=false -playerData.Tlso=timer.getTime() -playerData.Tgroove=nil -playerData.TIG0=nil -playerData.wire=nil -playerData.flag=-100 -playerData.debriefschedulerID=nil -if playerData.group:GetName():match("Groove")and playerData.passes==0 then -self:MessageToPlayer(playerData,"Group name contains \"Groove\". Happy groove testing.") -playerData.attitudemonitor=true -playerData.step=AIRBOSS.PatternStep.FINAL -self:_AddFlightToPatternQueue(playerData) -self.dTstatus=0.1 -end -return playerData -end -function AIRBOSS:_GetFlightFromGroupInQueue(group,queue) -if group then -local name=group:GetName() -for i,_flight in pairs(queue)do -local flight=_flight -if flight.groupname==name then -return flight,i -end -end -self:T2(self.lid..string.format("WARNING: Flight group %s could not be found in queue.",name)) -end -self:T2(self.lid..string.format("WARNING: Flight group could not be found in queue. Group is nil!")) -return nil,nil -end -function AIRBOSS:_GetFlightElement(unitname) -local unit=UNIT:FindByName(unitname) -if unit then -local flight=self:_GetFlightFromGroupInQueue(unit:GetGroup(),self.flights) -if flight then -for i,_element in pairs(flight.elements)do -local element=_element -if element.unit:GetName()==unitname then -return element,i,flight -end -end -self:T2(self.lid..string.format("WARNING: Flight element %s could not be found in flight group.",unitname,flight.groupname)) -end -end -return nil,nil,nil -end -function AIRBOSS:_RemoveFlightElement(unitname) -local element,idx,flight=self:_GetFlightElement(unitname) -if idx then -table.remove(flight.elements,idx) -return true -else -self:T("WARNING: Flight element could not be removed from flight group. Index=nil!") -return nil -end -end -function AIRBOSS:_InQueue(queue,group) -local name=group:GetName() -for _,_flight in pairs(queue)do -local flight=_flight -if name==flight.groupname then -return true -end -end -return false -end -function AIRBOSS:_RemoveDeadFlightGroups() -for i=#self.flight,1,-1 do -local flight=self.flights[i] -if not flight.group:IsAlive()then -self:T(string.format("Removing dead flight group %s from ALL flights table.",flight.groupname)) -table.remove(self.flights,i) -end -end -for i=#self.Qmarshal,1,-1 do -local flight=self.Qmarshal[i] -if not flight.group:IsAlive()then -self:T(string.format("Removing dead flight group %s from Marshal Queue table.",flight.groupname)) -table.remove(self.Qmarshal,i) -end -end -for i=#self.Qpattern,1,-1 do -local flight=self.Qpattern[i] -if not flight.group:IsAlive()then -self:T(string.format("Removing dead flight group %s from Pattern Queue table.",flight.groupname)) -table.remove(self.Qpattern,i) -end -end -end -function AIRBOSS:_GetLeadFlight(flight) -local lead=flight -if flight.name~=flight.seclead then -lead=self.players[flight.seclead] -end -return lead -end -function AIRBOSS:_CheckSectionRecovered(flight) -if flight==nil then -return true -end -local lead=self:_GetLeadFlight(flight) -for _,_element in pairs(lead.elements)do -local element=_element -if not element.recovered then -return false -end -end -for _,_section in pairs(lead.section)do -local sectionmember=_section -for _,_element in pairs(sectionmember.elements)do -local element=_element -if not element.recovered then -return false -end -end -end -self:_RemoveFlightFromQueue(self.Qpattern,lead) -if self:_InQueue(self.Qmarshal,lead.group)then -self:E(self.lid..string.format("ERROR: lead flight group %s should not be in marshal queue",lead.groupname)) -self:_RemoveFlightFromMarshalQueue(lead,true) -end -if self:_InQueue(self.Qwaiting,lead.group)then -self:E(self.lid..string.format("ERROR: lead flight group %s should not be in pattern queue",lead.groupname)) -self:_RemoveFlightFromQueue(self.Qwaiting,lead) -end -return true -end -function AIRBOSS:_AddFlightToPatternQueue(flight) -table.insert(self.Qpattern,flight) -flight.flag=-1 -flight.time=timer.getAbsTime() -flight.recovered=false -for _,elem in pairs(flight.elements)do -elem.recoverd=false -end -for _,sec in pairs(flight.section)do -sec.flag=-1 -sec.time=timer.getAbsTime() -for _,elem in pairs(sec.elements)do -elem.recoverd=false -end -end -end -function AIRBOSS:_RecoveredElement(unit) -local element,idx,flight=self:_GetFlightElement(unit:GetName()) -if element then -element.recovered=true -end -return flight -end -function AIRBOSS:_RemoveFlightFromMarshalQueue(flight,nopattern) -local removed,idx=self:_RemoveFlightFromQueue(self.Qmarshal,flight) -if removed then -flight.holding=nil -self:_CollapseMarshalStack(flight,nopattern) -if flight.case==1 and#self.Qwaiting>0 then -local nextflight=self.Qwaiting[1] -local freestack=self:_GetFreeStack(nextflight.ai) -if nextflight.ai then -self:_MarshalAI(nextflight,freestack) -else -self:_MarshalPlayer(nextflight,freestack) -end -self:_RemoveFlightFromQueue(self.Qwaiting,nextflight) -end -end -return removed,idx -end -function AIRBOSS:_RemoveFlightFromQueue(queue,flight) -for i,_flight in pairs(queue)do -local qflight=_flight -if qflight.groupname==flight.groupname then -self:T(self.lid..string.format("Removing flight group %s from queue.",flight.groupname)) -table.remove(queue,i) -return true,i -end -end -return false,nil -end -function AIRBOSS:_RemoveUnitFromFlight(unit) -if unit and unit:IsInstanceOf("UNIT")then -local group=unit:GetGroup() -if group then -local flight=self:_GetFlightFromGroupInQueue(group,self.flights) -if flight then -local removed=self:_RemoveFlightElement(unit:GetName()) -if removed then -local _,nunits=self:_GetFlightUnits(flight,not flight.ai) -local nelements=#flight.elements -self:T(self.lid..string.format("Removed unit %s: nunits=%d, nelements=%d",unit:GetName(),nunits,nelements)) -if nunits==0 or nelements==0 then -self:_RemoveFlight(flight) -end -end -end -end -end -end -function AIRBOSS:_RemoveFlightFromSection(flight) -if flight.name~=flight.seclead then -local lead=self.players[flight.seclead] -if lead then -for i,sec in pairs(lead.section)do -local sectionmember=sec -if sectionmember.name==flight.name then -table.remove(lead.section,i) -break -end -end -end -end -end -function AIRBOSS:_UpdateFlightSection(flight) -if flight.seclead==flight.name then -if#flight.section>=1 then -local newlead=flight.section[1] -newlead.seclead=newlead.name -for i=2,#flight.section do -local member=flight.section[i] -table.insert(newlead.section,member) -member.seclead=newlead.name -end -end -flight.section={} -else -self:_RemoveFlightFromSection(flight) -end -end -function AIRBOSS:_RemoveFlight(flight,completely) -self:F(self.lid..string.format("Removing flight %s, ai=%s completely=%s.",tostring(flight.groupname),tostring(flight.ai),tostring(completely))) -self:_RemoveFlightFromMarshalQueue(flight,true) -self:_RemoveFlightFromQueue(self.Qpattern,flight) -self:_RemoveFlightFromQueue(self.Qwaiting,flight) -self:_RemoveFlightFromQueue(self.Qspinning,flight) -if flight.ai then -self:_RemoveFlightFromQueue(self.flights,flight) -else -local grades=self.playerscores[flight.name] -if grades and#grades>0 then -while#grades>0 and grades[#grades].finalscore==nil do -table.remove(grades,#grades) -end -end -if completely then -self:_UpdateFlightSection(flight) -self:_RemoveFlightFromQueue(self.flights,flight) -local playerdata=self.players[flight.name] -if playerdata then -self:T(self.lid..string.format("Removing player %s completely.",flight.name)) -self.players[flight.name]=nil -end -flight=nil -else -self:_SetPlayerStep(flight,AIRBOSS.PatternStep.UNDEFINED) -for _,sectionmember in pairs(flight.section)do -self:_SetPlayerStep(sectionmember,AIRBOSS.PatternStep.UNDEFINED) -self:_RemoveFlightFromQueue(self.Qspinning,sectionmember) -end -self:_RemoveFlightFromSection(flight) -end -end -end -function AIRBOSS:_CheckPlayerStatus() -for _playerName,_playerData in pairs(self.players)do -local playerData=_playerData -if playerData then -local unit=playerData.unit -if unit and unit:IsAlive()then -if unit:IsInZone(self.zoneCCA)then -if playerData.attitudemonitor then -self:_AttitudeMonitor(playerData) -end -self:_CheckPlayerPatternDistance(playerData) -self:_CheckFoulDeck(playerData) -if playerData.step==AIRBOSS.PatternStep.UNDEFINED then -elseif playerData.step==AIRBOSS.PatternStep.REFUELING then -elseif playerData.step==AIRBOSS.PatternStep.SPINNING then -self:_Spinning(playerData) -elseif playerData.step==AIRBOSS.PatternStep.HOLDING then -self:_Holding(playerData) -elseif playerData.step==AIRBOSS.PatternStep.WAITING then -self:_Waiting(playerData) -elseif playerData.step==AIRBOSS.PatternStep.COMMENCING then -self:_Commencing(playerData,true) -elseif playerData.step==AIRBOSS.PatternStep.BOLTER then -self:_BolterPattern(playerData) -elseif playerData.step==AIRBOSS.PatternStep.PLATFORM then -self:_Platform(playerData) -elseif playerData.step==AIRBOSS.PatternStep.ARCIN then -self:_ArcInTurn(playerData) -elseif playerData.step==AIRBOSS.PatternStep.ARCOUT then -self:_ArcOutTurn(playerData) -elseif playerData.step==AIRBOSS.PatternStep.DIRTYUP then -self:_DirtyUp(playerData) -elseif playerData.step==AIRBOSS.PatternStep.BULLSEYE then -self:_Bullseye(playerData) -elseif playerData.step==AIRBOSS.PatternStep.INITIAL then -self:_Initial(playerData) -elseif playerData.step==AIRBOSS.PatternStep.BREAKENTRY then -self:_BreakEntry(playerData) -elseif playerData.step==AIRBOSS.PatternStep.EARLYBREAK then -self:_Break(playerData,AIRBOSS.PatternStep.EARLYBREAK) -elseif playerData.step==AIRBOSS.PatternStep.LATEBREAK then -self:_Break(playerData,AIRBOSS.PatternStep.LATEBREAK) -elseif playerData.step==AIRBOSS.PatternStep.ABEAM then -self:_Abeam(playerData) -elseif playerData.step==AIRBOSS.PatternStep.NINETY then -self:_CheckForLongDownwind(playerData) -self:_Ninety(playerData) -elseif playerData.step==AIRBOSS.PatternStep.WAKE then -self:_Wake(playerData) -elseif playerData.step==AIRBOSS.PatternStep.EMERGENCY then -self:_Final(playerData,true) -elseif playerData.step==AIRBOSS.PatternStep.FINAL then -self:_Final(playerData) -elseif playerData.step==AIRBOSS.PatternStep.GROOVE_XX or -playerData.step==AIRBOSS.PatternStep.GROOVE_IM or -playerData.step==AIRBOSS.PatternStep.GROOVE_IC or -playerData.step==AIRBOSS.PatternStep.GROOVE_AR or -playerData.step==AIRBOSS.PatternStep.GROOVE_AL or -playerData.step==AIRBOSS.PatternStep.GROOVE_LC or -playerData.step==AIRBOSS.PatternStep.GROOVE_IW then -self:_Groove(playerData) -elseif playerData.step==AIRBOSS.PatternStep.DEBRIEF then -playerData.debriefschedulerID=self:ScheduleOnce(5,self._Debrief,self,playerData) -playerData.step=AIRBOSS.PatternStep.UNDEFINED -else -self:E(self.lid..string.format("ERROR: Unknown player step %s. Please report!",tostring(playerData.step))) -end -self:_CheckMissedStepOnEntry(playerData) -else -self:T2(self.lid.."WARNING: Player unit not inside the CCA!") -end -else -self:T(self.lid.."WARNING: Player unit is not alive!") -end -end -end -end -function AIRBOSS:_CheckMissedStepOnEntry(playerData) -local rightcase=playerData.case>1 -local rightqueue=self:_InQueue(self.Qpattern,playerData.group) -local rightflag=playerData.flag~=-42 -local step=playerData.step -local missedstep=step==AIRBOSS.PatternStep.PLATFORM or step==AIRBOSS.PatternStep.ARCIN or step==AIRBOSS.PatternStep.ARCOUT or step==AIRBOSS.PatternStep.DIRTYUP -if rightcase and rightqueue and rightflag then -local zone=nil -if playerData.case==2 and missedstep then -zone=self:_GetZoneInitial(playerData.case) -elseif playerData.case==3 and missedstep then -zone=self:_GetZoneBullseye(playerData.case) -end -if zone then -local inzone=playerData.unit:IsInZone(zone) -local relheading=self:_GetRelativeHeading(playerData.unit,false) -if inzone and math.abs(relheading)<60 then -local text=string.format("you missed an important step in the pattern!\nYour next step would have been %s.",playerData.step) -self:MessageToPlayer(playerData,text,"AIRBOSS",nil,5) -if playerData.case==2 then -playerData.step=AIRBOSS.PatternStep.INITIAL -elseif playerData.case==3 then -playerData.step=AIRBOSS.PatternStep.BULLSEYE -end -playerData.flag=-42 -end -end -end -end -function AIRBOSS:_SetTimeInGroove(playerData) -if playerData.TIG0 then -playerData.Tgroove=timer.getTime()-playerData.TIG0 -else -playerData.Tgroove=999 -end -end -function AIRBOSS:_GetTimeInGroove(playerData) -local Tgroove=999 -if playerData.TIG0 then -Tgroove=timer.getTime()-playerData.TIG0 -end -return Tgroove -end -function AIRBOSS:OnEventBirth(EventData) -self:F3({eventbirth=EventData}) -if EventData==nil then -self:E(self.lid.."ERROR: EventData=nil in event BIRTH!") -self:E(EventData) -return -end -if EventData.IniUnit==nil and(not EventData.IniObjectCategory==Object.Category.STATIC)then -self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!") -self:E(EventData) -return -end -if EventData.IniObjectCategory~=Object.Category.UNIT then return end -local _unitName=EventData.IniUnitName -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -self:T(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName)) -self:T(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName)) -self:T(self.lid.."BIRTH: player = "..tostring(_playername)) -if _unit and _playername then -local _uid=_unit:GetID() -local _group=_unit:GetGroup() -local _callsign=_unit:GetCallsign() -local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.",_playername,_callsign,_unitName,_group:GetName()) -self:T(self.lid..text) -MESSAGE:New(text,5):ToAllIf(self.Debug) -local rightaircraft=self:_IsCarrierAircraft(_unit) -if rightaircraft==false then -local text=string.format("Player aircraft type %s not supported by AIRBOSS class.",_unit:GetTypeName()) -MESSAGE:New(text,30):ToAllIf(self.Debug) -self:T2(self.lid..text) -return -end -if self:GetCoalition()~=_unit:GetCoalition()then -local text=string.format("Player entered aircraft of other coalition.") -MESSAGE:New(text,30):ToAllIf(self.Debug) -self:T(self.lid..text) -return -end -self:_AddF10Commands(_unitName) -self:ScheduleOnce(1,self._NewPlayer,self,_unitName) -end -end -function AIRBOSS:OnEventLand(EventData) -self:F3({eventland=EventData}) -if EventData==nil then -self:E(self.lid.."ERROR: EventData=nil in event LAND!") -self:E(EventData) -return -end -if EventData.IniUnit==nil then -self:E(self.lid.."ERROR: EventData.IniUnit=nil in event LAND!") -self:E(EventData) -return -end -local _unitName=EventData.IniUnitName -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -self:T(self.lid.."LAND: unit = "..tostring(EventData.IniUnitName)) -self:T(self.lid.."LAND: group = "..tostring(EventData.IniGroupName)) -self:T(self.lid.."LAND: player = "..tostring(_playername)) -local airbase=EventData.Place -if airbase==nil then -return -end -local airbasename=tostring(airbase:GetName()) -if airbasename==self.airbase:GetName()then -local stern=self:_GetSternCoord() -local zoneCarrier=self:_GetZoneCarrierBox() -if _unit and _playername then -local _uid=_unit:GetID() -local _group=_unit:GetGroup() -local _callsign=_unit:GetCallsign() -local text=string.format("Player %s, callsign %s unit %s (ID=%d) of group %s landed at airbase %s",_playername,_callsign,_unitName,_uid,_group:GetName(),airbasename) -self:T(self.lid..text) -MESSAGE:New(text,5,"DEBUG"):ToAllIf(self.Debug) -local playerData=self.players[_playername] -if playerData==nil then -self:E(self.lid..string.format("ERROR: playerData nil in landing event. unit=%s player=%s",tostring(_unitName),tostring(_playername))) -return -end -if _unit:IsInZone(zoneCarrier)then -if not playerData.valid then -local text=string.format("you missed at least one important step in the pattern!\nYour next step would have been %s.\nThis pass is INVALID.",playerData.step) -self:MessageToPlayer(playerData,text,"AIRBOSS",nil,30,true,5) -self:_RemoveFlightFromMarshalQueue(playerData,true) -self:_RemoveFlightFromQueue(self.Qpattern,playerData) -self:_RemoveFlightFromQueue(self.Qwaiting,playerData) -self:_RemoveFlightFromQueue(self.Qspinning,playerData) -self:_InitPlayer(playerData) -return -end -if playerData.landed then -self:E(self.lid..string.format("Player %s just landed a second time.",_playername)) -else -playerData.landed=true -playerData.attitudemonitor=false -local coord=playerData.unit:GetCoordinate() -local X,Z,rho,phi=self:_GetDistances(_unit) -local dist=coord:Get2DDistance(stern) -if self.Debug and false then -local lp=coord:MarkToAll("Landing coord.") -coord:SmokeGreen() -end -self:_SetTimeInGroove(playerData) -local text=string.format("Player %s AC type %s landed at dist=%.1f m. Tgroove=%.1f sec.",playerData.name,playerData.actype,dist,self:_GetTimeInGroove(playerData)) -text=text..string.format(" X=%.1f m, Z=%.1f m, rho=%.1f m.",X,Z,rho) -self:T(self.lid..text) -if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -self:RadioTransmission(self.LSORadio,self.LSOCall.IDLE,false,1,nil,true) -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF) -else -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.UNDEFINED) -self:ScheduleOnce(1,self._Trapped,self,playerData) -end -end -else -if playerData then -self:E(self.lid..string.format("Player %s did not land in carrier box zone. Maybe in the water near the carrier?",playerData.name)) -end -end -else -if self.carriertype~=AIRBOSS.CarrierType.INVINCIBLE or self.carriertype~=AIRBOSS.CarrierType.HERMES or self.carriertype~=AIRBOSS.CarrierType.TARAWA or self.carriertype~=AIRBOSS.CarrierType.AMERICA or self.carriertype~=AIRBOSS.CarrierType.JCARLOS or self.carriertype~=AIRBOSS.CarrierType.CANBERRA then -local coord=EventData.IniUnit:GetCoordinate() -local dist=coord:Get2DDistance(self:GetCoordinate()) -local wire=self:_GetWire(coord,0) -local _type=EventData.IniUnit:GetTypeName() -local text=string.format("AI unit %s of type %s landed at dist=%.1f m. Trapped wire=%d.",_unitName,_type,dist,wire) -self:T(self.lid..text) -end -local flight=self:_RecoveredElement(EventData.IniUnit) -self:_CheckSectionRecovered(flight) -end -end -end -function AIRBOSS:OnEventEngineShutdown(EventData) -self:F3({eventengineshutdown=EventData}) -if EventData==nil then -self:E(self.lid.."ERROR: EventData=nil in event ENGINESHUTDOWN!") -self:E(EventData) -return -end -if EventData.IniUnit==nil then -self:E(self.lid.."ERROR: EventData.IniUnit=nil in event ENGINESHUTDOWN!") -self:E(EventData) -return -end -local _unitName=EventData.IniUnitName -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -self:T3(self.lid.."ENGINESHUTDOWN: unit = "..tostring(EventData.IniUnitName)) -self:T3(self.lid.."ENGINESHUTDOWN: group = "..tostring(EventData.IniGroupName)) -self:T3(self.lid.."ENGINESHUTDOWN: player = "..tostring(_playername)) -if _unit and _playername then -self:T(self.lid..string.format("Player %s shut down its engines!",_playername)) -else -self:T(self.lid..string.format("AI unit %s shut down its engines!",_unitName)) -local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights) -if flight and flight.ai then -local recovered=self:_CheckSectionRecovered(flight) -if recovered then -self:T(self.lid..string.format("AI group %s completely recovered. Despawning group after engine shutdown event as requested in 5 seconds.",tostring(EventData.IniGroupName))) -self:_RemoveFlight(flight) -local istanker=self.tanker and self.tanker.tanker:GetName()==EventData.IniGroupName -local isawacs=self.awacs and self.awacs.tanker:GetName()==EventData.IniGroupName -if self.despawnshutdown and not(istanker or isawacs)then -EventData.IniGroup:Destroy(nil,5) -end -end -end -end -end -function AIRBOSS:OnEventTakeoff(EventData) -self:F3({eventtakeoff=EventData}) -if EventData==nil then -self:E(self.lid.."ERROR: EventData=nil in event TAKEOFF!") -self:E(EventData) -return -end -if EventData.IniUnit==nil then -self:E(self.lid.."ERROR: EventData.IniUnit=nil in event TAKEOFF!") -self:E(EventData) -return -end -local _unitName=EventData.IniUnitName -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -self:T3(self.lid.."TAKEOFF: unit = "..tostring(EventData.IniUnitName)) -self:T3(self.lid.."TAKEOFF: group = "..tostring(EventData.IniGroupName)) -self:T3(self.lid.."TAKEOFF: player = "..tostring(_playername)) -local airbase=EventData.Place -local airbasename="unknown" -if airbase then -airbasename=airbase:GetName() -end -if airbasename==self.airbase:GetName()then -if _unit and _playername then -self:T(self.lid..string.format("Player %s took off at %s!",_playername,airbasename)) -else -self:T2(self.lid..string.format("AI unit %s took off at %s!",_unitName,airbasename)) -local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights) -if flight then -for _,elem in pairs(flight.elements)do -local element=elem -element.ballcall=false -element.recovered=nil -end -end -end -end -end -function AIRBOSS:OnEventCrash(EventData) -self:F3({eventcrash=EventData}) -if EventData==nil then -self:E(self.lid.."ERROR: EventData=nil in event CRASH!") -self:E(EventData) -return -end -if EventData.IniUnit==nil then -self:E(self.lid.."ERROR: EventData.IniUnit=nil in event CRASH!") -self:E(EventData) -return -end -local _unitName=EventData.IniUnitName -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -self:T3(self.lid.."CRASH: unit = "..tostring(EventData.IniUnitName)) -self:T3(self.lid.."CRASH: group = "..tostring(EventData.IniGroupName)) -self:T3(self.lid.."CARSH: player = "..tostring(_playername)) -if _unit and _playername then -self:T(self.lid..string.format("Player %s crashed!",_playername)) -local flight=self.players[_playername] -if flight then -self:_RemoveFlight(flight,true) -end -else -self:T2(self.lid..string.format("AI unit %s crashed!",EventData.IniUnitName)) -self:_RemoveUnitFromFlight(EventData.IniUnit) -end -end -function AIRBOSS:OnEventEjection(EventData) -self:F3({eventland=EventData}) -if EventData==nil then -self:E(self.lid.."ERROR: EventData=nil in event EJECTION!") -self:E(EventData) -return -end -if EventData.IniUnit==nil then -self:E(self.lid.."ERROR: EventData.IniUnit=nil in event EJECTION!") -self:E(EventData) -return -end -local _unitName=EventData.IniUnitName -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -self:T3(self.lid.."EJECT: unit = "..tostring(EventData.IniUnitName)) -self:T3(self.lid.."EJECT: group = "..tostring(EventData.IniGroupName)) -self:T3(self.lid.."EJECT: player = "..tostring(_playername)) -if _unit and _playername then -self:T(self.lid..string.format("Player %s ejected!",_playername)) -local flight=self.players[_playername] -if flight then -self:_RemoveFlight(flight,true) -end -else -self:T(self.lid..string.format("AI unit %s ejected!",EventData.IniUnitName)) -self:_RemoveUnitFromFlight(EventData.IniUnit) -local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights) -self:_CheckSectionRecovered(flight) -end -end -function AIRBOSS:OnEventRemoveUnit(EventData) -self:F3({eventland=EventData}) -if EventData==nil then -self:E(self.lid.."ERROR: EventData=nil in event REMOVEUNIT!") -self:E(EventData) -return -end -if EventData.IniUnit==nil then -self:E(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!") -self:E(EventData) -return -end -local _unitName=EventData.IniUnitName -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -self:T3(self.lid.."EJECT: unit = "..tostring(EventData.IniUnitName)) -self:T3(self.lid.."EJECT: group = "..tostring(EventData.IniGroupName)) -self:T3(self.lid.."EJECT: player = "..tostring(_playername)) -if _unit and _playername then -self:T(self.lid..string.format("Player %s removed!",_playername)) -local flight=self.players[_playername] -if flight then -self:_RemoveFlight(flight,true) -end -else -self:T(self.lid..string.format("AI unit %s removed!",EventData.IniUnitName)) -self:_RemoveUnitFromFlight(EventData.IniUnit) -local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights) -self:_CheckSectionRecovered(flight) -end -end -function AIRBOSS:_PlayerLeft(EventData) -self:F3({eventleave=EventData}) -if EventData==nil then -self:E(self.lid.."ERROR: EventData=nil in event PLAYERLEFTUNIT!") -self:E(EventData) -return -end -if EventData.IniUnit==nil then -self:E(self.lid.."ERROR: EventData.IniUnit=nil in event PLAYERLEFTUNIT!") -self:E(EventData) -return -end -local _unitName=EventData.IniUnitName -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -self:T3(self.lid.."PLAYERLEAVEUNIT: unit = "..tostring(EventData.IniUnitName)) -self:T3(self.lid.."PLAYERLEAVEUNIT: group = "..tostring(EventData.IniGroupName)) -self:T3(self.lid.."PLAYERLEAVEUNIT: player = "..tostring(_playername)) -if _unit and _playername then -self:T(self.lid..string.format("Player %s left unit %s!",_playername,_unitName)) -local flight=self.players[_playername] -if flight then -self:_RemoveFlight(flight,true) -end -end -end -function AIRBOSS:OnEventMissionEnd(EventData) -self:T3(self.lid.."Mission Ended") -end -function AIRBOSS:_Spinning(playerData) -local SpinIt={} -SpinIt.name="Spinning" -SpinIt.Xmin=-UTILS.NMToMeters(6) -SpinIt.Xmax=UTILS.NMToMeters(5) -SpinIt.Zmin=-UTILS.NMToMeters(6) -SpinIt.Zmax=UTILS.NMToMeters(2) -SpinIt.LimitXmin=-100 -SpinIt.LimitXmax=nil -SpinIt.LimitZmin=-UTILS.NMToMeters(1) -SpinIt.LimitZmax=nil -local X,Z,rho,phi=self:_GetDistances(playerData.unit) -if self:_CheckLimits(X,Z,SpinIt)then -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.INITIAL) -self:_RemoveFlightFromQueue(self.Qspinning,playerData) -end -end -function AIRBOSS:_Waiting(playerData) -local radius=UTILS.NMToMeters(10) -local zone=ZONE_RADIUS:New("Carrier 10 NM Zone",self.carrier:GetVec2(),radius) -local inzone=playerData.unit:IsInZone(zone) -local Twaiting=timer.getAbsTime()-playerData.time -if inzone and Twaiting>3*60 and not playerData.warning then -local text=string.format("You are supposed to wait outside the 10 NM zone.") -self:MessageToPlayer(playerData,text,"AIRBOSS") -playerData.warning=true -end -if inzone==false and playerData.warning==true then -playerData.warning=nil -end -end -function AIRBOSS:_Holding(playerData) -local unit=playerData.unit -local stack=playerData.flag -if stack<=0 then -local text=string.format("ERROR: player %s in step %s is holding but has stack=%s (<=0)",playerData.name,playerData.step,tostring(stack)) -self:E(self.lid..text) -end -local patternalt=self:_GetMarshalAltitude(stack,playerData.case) -local playeralt=unit:GetAltitude() -local zoneHolding=self:_GetZoneHolding(playerData.case,stack) -if zoneHolding==nil then -self:E(self.lid.."ERROR: zoneHolding is nil!") -self:E({playerData=playerData}) -return -end -local inholdingzone=unit:IsInZone(zoneHolding) -local altdiff=playeralt-patternalt -local altgood=UTILS.FeetToMeters(500) -if playerData.difficulty==AIRBOSS.Difficulty.HARD then -altgood=UTILS.FeetToMeters(200) -elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then -altgood=UTILS.FeetToMeters(350) -elseif playerData.difficulty==AIRBOSS.Difficulty.EASY then -altgood=UTILS.FeetToMeters(500) -end -local altback=altgood*0.5 -local justcollapsed=false -if self.Tcollapse then -local dT=timer.getTime()-self.Tcollapse -if dT<=90 then -justcollapsed=true -end -end -local goodalt=math.abs(altdiff)altgood then -if not playerData.warning then -text=text..string.format("You left your assigned altitude. Descent to angels %d.",angels) -playerData.warning=true -end -elseif altdiff<-altgood then -if not playerData.warning then -text=text..string.format("You left your assigned altitude. Climb to angels %d.",angels) -playerData.warning=true -end -end -end -if playerData.warning and math.abs(altdiff)<=altback then -text=text..string.format("Altitude is looking good again.") -playerData.warning=nil -end -elseif playerData.holding==false then -if inholdingzone then -text=text..string.format("You are back in the holding zone. Now stay there!") -playerData.holding=true -else -self:T3("Player still outside the holding zone. What are you doing man?!") -end -elseif playerData.holding==nil then -if inholdingzone then -playerData.holding=true -text=text..string.format("You arrived at the holding zone.") -if goodalt then -text=text..string.format(" Altitude is good.") -else -if altdiff<0 then -text=text..string.format(" But you're too low.") -else -text=text..string.format(" But you're too high.") -end -text=text..string.format("\nCurrently assigned altitude is %d ft.",UTILS.MetersToFeet(patternalt)) -playerData.warning=true -end -else -self:T3("Waiting for player to arrive in the holding zone.") -end -end -if playerData.showhints then -self:MessageToPlayer(playerData,text,"MARSHAL") -end -end -function AIRBOSS:_Commencing(playerData,zonecheck) -if zonecheck then -local zoneCommence=self:_GetZoneCommence(playerData.case,playerData.flag) -local inzone=playerData.unit:IsInZone(zoneCommence) -if not inzone then -if timer.getAbsTime()-playerData.time>180 then -self:_MarshalCallClearedForRecovery(playerData.onboard,playerData.case) -playerData.time=timer.getAbsTime() -end -return -end -end -self:_RemoveFlightFromMarshalQueue(playerData) -self:_InitPlayer(playerData) -if playerData.difficulty~=AIRBOSS.Difficulty.HARD then -local text="" -if playerData.case==1 then -text=text.."Proceed to initial." -else -text=text.."Descent to platform." -if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then -text=text.." VSI 4000 ft/min until you reach 5000 ft." -end -end -self:MessageToPlayer(playerData,text,"MARSHAL") -end -local nextstep -if playerData.case==1 then -nextstep=AIRBOSS.PatternStep.INITIAL -else -nextstep=AIRBOSS.PatternStep.PLATFORM -end -self:_SetPlayerStep(playerData,nextstep) -for i,_flight in pairs(playerData.section)do -local flight=_flight -self:_Commencing(flight,false) -end -end -function AIRBOSS:_Initial(playerData) -local inzone=playerData.unit:IsInZone(self:_GetZoneInitial(playerData.case)) -local relheading=self:_GetRelativeHeading(playerData.unit,false) -local altitude=playerData.unit:GetAltitude() -if inzone and math.abs(relheading)<60 and altitude<=self.initialmaxalt then -if playerData.showhints then -local hint=string.format("Initial") -if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then -hint=hint.." - Hook down, SAS on, Wing Sweep 68°!" -else -hint=hint.." - Hook down!" -end -end -self:MessageToPlayer(playerData,hint,"MARSHAL") -end -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.BREAKENTRY) -return true -end -return false -end -function AIRBOSS:_CheckCorridor(playerData) -local validzone=self:_GetZoneCorridor(playerData.case) -local invalid=playerData.unit:IsNotInZone(validzone) -if invalid and(not playerData.warning)then -self:MessageToPlayer(playerData,"you left the approach corridor!","AIRBOSS") -playerData.warning=true -end -if(not invalid)and playerData.warning then -self:MessageToPlayer(playerData,"you're back in the approach corridor.","AIRBOSS") -playerData.warning=false -end -end -function AIRBOSS:_Platform(playerData) -self:_CheckCorridor(playerData) -local inzone=playerData.unit:IsInZone(self:_GetZonePlatform(playerData.case)) -if inzone then -self:_PlayerHint(playerData) -local nextstep -if math.abs(self.holdingoffset)>0 and playerData.case>1 then -nextstep=AIRBOSS.PatternStep.ARCIN -else -if playerData.case==2 then -nextstep=AIRBOSS.PatternStep.INITIAL -elseif playerData.case==3 then -nextstep=AIRBOSS.PatternStep.DIRTYUP -end -end -self:_SetPlayerStep(playerData,nextstep) -end -end -function AIRBOSS:_ArcInTurn(playerData) -self:_CheckCorridor(playerData) -local inzone=playerData.unit:IsInZone(self:_GetZoneArcIn(playerData.case)) -if inzone then -self:_PlayerHint(playerData) -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.ARCOUT) -end -end -function AIRBOSS:_ArcOutTurn(playerData) -self:_CheckCorridor(playerData) -local inzone=playerData.unit:IsInZone(self:_GetZoneArcOut(playerData.case)) -if inzone then -self:_PlayerHint(playerData) -local nextstep -if playerData.case==3 then -nextstep=AIRBOSS.PatternStep.DIRTYUP -else -nextstep=AIRBOSS.PatternStep.INITIAL -end -self:_SetPlayerStep(playerData,nextstep) -end -end -function AIRBOSS:_DirtyUp(playerData) -self:_CheckCorridor(playerData) -local inzone=playerData.unit:IsInZone(self:_GetZoneDirtyUp(playerData.case)) -if inzone then -self:_PlayerHint(playerData) -if playerData.actype==AIRBOSS.AircraftCarrier.HORNET -or playerData.actype==AIRBOSS.AircraftCarrier.F14A -or playerData.actype==AIRBOSS.AircraftCarrier.F14B -or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE -or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF -or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER -then -local callsay=self:_NewRadioCall(self.MarshalCall.SAYNEEDLES,nil,nil,5,playerData.onboard) -local callfly=self:_NewRadioCall(self.MarshalCall.FLYNEEDLES,nil,nil,5,playerData.onboard) -self:RadioTransmission(self.MarshalRadio,callsay,false,55,nil,true) -self:RadioTransmission(self.MarshalRadio,callfly,false,60,nil,true) -end -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.BULLSEYE) -end -end -function AIRBOSS:_Bullseye(playerData) -self:_CheckCorridor(playerData) -local inzone=playerData.unit:IsInZone(self:_GetZoneBullseye(playerData.case)) -local relheading=self:_GetRelativeHeading(playerData.unit,true) -if inzone and math.abs(relheading)<60 then -self:_PlayerHint(playerData) -if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.JCARLOS then -self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT5,nil,nil,nil,true) -elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.CANBERRA then -self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT5,nil,nil,nil,true) -elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then -self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT75,nil,nil,nil,true) -end -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_XX) -end -end -function AIRBOSS:_BolterPattern(playerData) -local X,Z,rho,phi=self:_GetDistances(playerData.unit) -local Bolter={} -Bolter.name="Bolter Pattern" -Bolter.Xmin=-UTILS.NMToMeters(5) -Bolter.Xmax=UTILS.NMToMeters(3) -Bolter.Zmin=-UTILS.NMToMeters(5) -Bolter.Zmax=UTILS.NMToMeters(1) -Bolter.LimitXmin=100 -Bolter.LimitXmax=nil -Bolter.LimitZmin=nil -Bolter.LimitZmax=nil -if self:_CheckLimits(X,Z,Bolter)then -local nextstep -if playerData.case<3 then -nextstep=AIRBOSS.PatternStep.ABEAM -else -nextstep=AIRBOSS.PatternStep.BULLSEYE -end -self:_SetPlayerStep(playerData,nextstep) -end -end -function AIRBOSS:_BreakEntry(playerData) -local X,Z=self:_GetDistances(playerData.unit) -if self:_CheckAbort(X,Z,self.BreakEntry)then -self:_AbortPattern(playerData,X,Z,self.BreakEntry,true) -return -end -if self:_CheckLimits(X,Z,self.BreakEntry)then -self:_PlayerHint(playerData) -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK) -end -end -function AIRBOSS:_Break(playerData,part) -local X,Z=self:_GetDistances(playerData.unit) -local breakpoint=self.BreakEarly -if part==AIRBOSS.PatternStep.LATEBREAK then -breakpoint=self.BreakLate -end -if self:_CheckAbort(X,Z,breakpoint)then -self:_AbortPattern(playerData,X,Z,breakpoint,true) -return -end -local tooclose=false -if part==AIRBOSS.PatternStep.LATEBREAK then -local close=0.8 -if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then -close=0.5 -end -if X<0 and Z90 and self:_CheckLimits(X,Z,self.Wake)then -self:MessageToPlayer(playerData,"you are already at the wake and have not passed the 90. Turn faster next time!","LSO") -self:RadioTransmission(self.LSORadio,self.LSOCall.DEPARTANDREENTER,nil,nil,nil,true) -playerData.wop=true -self:_AddToDebrief(playerData,"Overshoot at wake - Pattern Waveoff!") -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF) -end -end -function AIRBOSS:_Wake(playerData) -local X,Z=self:_GetDistances(playerData.unit) -if self:_CheckAbort(X,Z,self.Wake)then -self:_AbortPattern(playerData,X,Z,self.Wake,true) -return -end -if self:_CheckLimits(X,Z,self.Wake)then -self:_PlayerHint(playerData) -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.FINAL) -end -end -function AIRBOSS:_GetGrooveData(playerData) -local X,Z=self:_GetDistances(playerData.unit) -local stern=self:_GetSternCoord() -local rho=stern:Get2DDistance(playerData.unit:GetCoordinate()) -local astern=X=RAR and rho<=RIC and not playerData.waveoff then -local waveoff=self:_CheckWaveOff(glideslopeError,lineupError,AoA,playerData) -if waveoff then -self:T3(self.lid..string.format("Waveoff distance rho=%.1f m",rho)) -self:RadioTransmission(self.LSORadio,self.LSOCall.WAVEOFF,nil,nil,nil,true) -playerData.Tlso=timer.getTime() -playerData.waveoff=true -return -end -end -groovedata.Step=playerData.step -if rho>=RAR and rho=RAR and rho<=RIM then -if gd.LUE>0.22 and lineupError<-0.22 then -env.info" Drift Right across centre ==> DR-" -gd.Drift=" DR" -self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) -elseif gd.LUE<-0.22 and lineupError>0.22 then -env.info" Drift Left ==> DL-" -gd.Drift=" DL" -self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) -elseif gd.LUE>0.13 and lineupError<-0.14 then -env.info" Little Drift Right across centre ==> (DR-)" -gd.Drift=" (DR)" -self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) -elseif gd.LUE<-0.13 and lineupError>0.14 then -env.info" Little Drift Left across centre ==> (DL-)" -gd.Drift=" (DL)" -self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) -end -end -if math.abs(lineupError)>math.abs(gd.LUE)then -self:T(self.lid..string.format("Got bigger LUE at step %s, d=%.3f: LUE %.3f>%.3f",gs,d,lineupError,gd.LUE)) -gd.LUE=lineupError -end -if gd.GSE>0.4 and glideslopeError<-0.3 then -gd.FlyThrough="\\" -self:T(self.lid..string.format("Got Fly through DOWN at step %s, d=%.3f: Max GSE=%.3f, lower GSE=%.3f",gs,d,gd.GSE,glideslopeError)) -elseif gd.GSE<-0.3 and glideslopeError>0.4 then -gd.FlyThrough="/" -self:E(self.lid..string.format("Got Fly through UP at step %s, d=%.3f: Min GSE=%.3f, lower GSE=%.3f",gs,d,gd.GSE,glideslopeError)) -end -if math.abs(glideslopeError)>math.abs(gd.GSE)then -self:T(self.lid..string.format("Got bigger GSE at step %s, d=%.3f: GSE |%.3f|>|%.3f|",gs,d,glideslopeError,gd.GSE)) -gd.GSE=glideslopeError -end -local aircraftaoa=self:_GetAircraftAoA(playerData) -local aoaopt=aircraftaoa.OnSpeed -if math.abs(AoA-aoaopt)>math.abs(gd.AoA-aoaopt)then -self:T(self.lid..string.format("Got bigger AoA error at step %s, d=%.3f: AoA %.3f>%.3f.",gs,d,AoA,gd.AoA)) -gd.AoA=AoA -end -end -local deltaT=timer.getTime()-playerData.Tlso -local _advice=true -if playerData.TIG0==nil and playerData.difficulty~=AIRBOSS.Difficulty.EASY then -_advice=false -end -if deltaT>=self.LSOdT and _advice then -self:_LSOadvice(playerData,glideslopeError,lineupError) -end -end -if X>self.carrierparam.totlength+self.carrierparam.sterndist then -if playerData.waveoff then -if playerData.landed then -self:_AddToDebrief(playerData,"You were waved off but landed anyway. Airboss wants to talk to you!") -else -self:_AddToDebrief(playerData,"You were waved off.") -end -elseif playerData.boltered then -self:_AddToDebrief(playerData,"You boltered.") -else -self:T("Player was not waved off but flew past the carrier without landing ==> Own wave off!") -self:_AddToDebrief(playerData,"Own waveoff.") -playerData.owo=true -end -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF) -end -end -function AIRBOSS:_CheckWaveOff(glideslopeError,lineupError,AoA,playerData) -local waveoff=false -local glMax=1.8 -local glMin=-1.2 -local luAbs=3.0 -if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then -glMax=2.6 -glMin=-2.2 -luAbs=4.1 -end -if glideslopeError>glMax then -local text=string.format("\n- Waveoff due to glideslope error %.2f > %.1f degrees!",glideslopeError,glMax) -self:T(self.lid..string.format("%s: %s",playerData.name,text)) -self:_AddToDebrief(playerData,text) -waveoff=true -elseif glideslopeErrorluAbs then -local text=string.format("\n- Waveoff due to line up error |%.1f| > %.1f degrees!",lineupError,luAbs) -self:T(self.lid..string.format("%s: %s",playerData.name,text)) -self:_AddToDebrief(playerData,text) -waveoff=true -end -if playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then -local aoaac=self:_GetAircraftAoA(playerData) -if AoAaoaac.SLOW then -local text=string.format("\n- Waveoff due to AoA %.1f > %.1f!",AoA,aoaac.SLOW) -self:T(self.lid..string.format("%s: %s",playerData.name,text)) -self:_AddToDebrief(playerData,text) -waveoff=true -end -end -return waveoff -end -function AIRBOSS:_CheckFoulDeck(playerData) -local check=false -if playerData.step==AIRBOSS.PatternStep.GROOVE_IM or playerData.step==AIRBOSS.PatternStep.GROOVE_IC then -check=true -end -if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then -if playerData.step==AIRBOSS.PatternStep.GROOVE_AR or playerData.step==AIRBOSS.PatternStep.GROOVE_AL then -check=true -end -end -if playerData.wofd==true or check==false then -return -end -local runway=self:_GetZoneRunwayBox() -if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then -runway=self:_GetZoneLandingSpot() -end -local R=250 -self:T(self.lid..string.format("Foul deck check: Scanning Carrier Runway Area. Radius=%.1f m.",R)) -local _,_,_,unitscan=self:GetCoordinate():ScanObjects(R,true,false,false) -local fouldeck=false -local foulunit=nil -for _,_unit in pairs(unitscan)do -local unit=_unit -local inzone=unit:IsInZone(runway) -local isaircraft=unit:IsAir() -local isairborn=unit:InAir() -if inzone and isaircraft and not isairborn then -local text=string.format("Unit %s on landing runway ==> Foul deck!",unit:GetName()) -self:T(self.lid..text) -MESSAGE:New(text,10):ToAllIf(self.Debug) -if self.Debug then -runway:FlareZone(FLARECOLOR.Red,30) -end -fouldeck=true -foulunit=unit -end -end -if playerData and fouldeck then -local text=string.format("Foul deck waveoff due to aircraft %s!",foulunit:GetName()) -self:T(self.lid..string.format("%s: %s",playerData.name,text)) -self:_AddToDebrief(playerData,text) -self:RadioTransmission(self.LSORadio,self.LSOCall.FOULDECK,false,1) -self:RadioTransmission(self.LSORadio,self.LSOCall.WAVEOFF,false,1.2,nil,true) -if playerData.showhints then -local text=string.format("overfly landing area and enter bolter pattern.") -self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3) -end -playerData.wofd=true -playerData.step=AIRBOSS.PatternStep.DEBRIEF -playerData.warning=nil -playerData.valid=false -if foulunit then -local foulflight=self:_GetFlightFromGroupInQueue(foulunit:GetGroup(),self.flights) -if foulflight and not foulflight.ai then -self:MessageToPlayer(foulflight,"move your ass from my runway. NOW!","AIRBOSS") -end -end -end -return fouldeck -end -function AIRBOSS:_GetSternCoord() -local hdg=self.carrier:GetHeading() -local FB=self:GetFinalBearing() -local case=self.case -self.sterncoord:UpdateFromCoordinate(self:GetCoordinate()) -if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -if case==3 then -self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(8,FB-90,true,true) -elseif case==2 or case==1 then -self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(8,FB-90,true,true) -end -elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then -self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(7,FB+90,true,true) -elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then -self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(7.5,FB+90,true,true) -else -self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(9.5,FB+90,true,true) -end -self.sterncoord:SetAltitude(self.carrierparam.deckheight) -return self.sterncoord -end -function AIRBOSS:_GetWireFromDrawArg() -local wireArgs={} -wireArgs[1]=141 -wireArgs[2]=142 -wireArgs[3]=143 -wireArgs[4]=144 -for wire,drawArg in pairs(wireArgs)do -local value=self.carrier:GetDrawArgumentValue(drawArg) -if math.abs(value)>0.001 then -return wire -end -end -return 99 -end -function AIRBOSS:_GetWire(Lcoord,dc) -local FB=self:GetFinalBearing() -local Scoord=self:_GetSternCoord() -local Ldist=Lcoord:Get2DDistance(Scoord) -dc=dc or 65 -local d=Ldist-dc -if self.mpWireCorrection then -d=d-self.mpWireCorrection -end -local w1=self.carrierparam.wire1 -local w2=self.carrierparam.wire2 -local w3=self.carrierparam.wire3 -local w4=self.carrierparam.wire4 -local wire -if d wire=%d (dc=%.1f)",Ldist,Ldist-dc,wire,dc)) -return wire -end -function AIRBOSS:_Trapped(playerData) -if playerData.unit:InAir()==false then -local unit=playerData.unit -local coord=unit:GetCoordinate() -local v=unit:GetVelocityKMH()-self.carrier:GetVelocityKMH() -local stern=self:_GetSternCoord() -local s=stern:Get2DDistance(coord) -local dcorr=100 -if playerData.actype==AIRBOSS.AircraftCarrier.HORNET -or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE -or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF -or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER then -dcorr=100 -elseif playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then -dcorr=100 -elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then -dcorr=56 -elseif playerData.actype==AIRBOSS.AircraftCarrier.T45C then -dcorr=56 -end -local wire=self:_GetWire(coord,dcorr) -local text=string.format("Player %s _Trapped: v=%.1f km/h, s-dcorr=%.1f m ==> wire=%d (dcorr=%d)",playerData.name,v,s-dcorr,wire,dcorr) -self:T(self.lid..text) -if v>5 then -if wire>4 and v>10 and not playerData.warning then -self:RadioTransmission(self.LSORadio,self.LSOCall.BOLTER,nil,nil,nil,true) -playerData.warning=true -end -self:ScheduleOnce(0.1,self._Trapped,self,playerData) -return -end -if self.Debug then -coord:SmokeBlue() -coord:MarkToAll(text) -stern:MarkToAll("Stern") -end -playerData.wire=wire -local text=string.format("Trapped %d-wire.",wire) -if wire==3 then -text=text.." Well done!" -elseif wire==2 then -text=text.." Not bad, maybe you even get the 3rd next time." -elseif wire==4 then -text=text.." That was scary. You can do better than this!" -elseif wire==1 then -text=text.." Try harder next time!" -end -self:MessageToPlayer(playerData,text,"LSO","") -local hint=string.format("Trapped %d-wire.",wire) -self:_AddToDebrief(playerData,hint,"Groove: IW") -else -local text=string.format("Player %s boltered in trapped function.",playerData.name) -self:T(self.lid..text) -MESSAGE:New(text,5,"DEBUG"):ToAllIf(self.debug) -playerData.boltered=true -end -playerData.step=AIRBOSS.PatternStep.DEBRIEF -playerData.warning=nil -end -function AIRBOSS:_GetZoneInitial(case) -self.zoneInitial=self.zoneInitial or ZONE_POLYGON_BASE:New("Zone CASE I/II Initial") -local radial=self:GetRadial(2,false,false) -local cv=self:GetCoordinate() -local vec2={} -if case==1 then -local c1=cv:Translate(UTILS.NMToMeters(0.5),radial-90) -local c2=cv:Translate(UTILS.NMToMeters(1.3),radial-90):Translate(UTILS.NMToMeters(3),radial) -local c3=cv:Translate(UTILS.NMToMeters(0.4),radial+90):Translate(UTILS.NMToMeters(3),radial) -local c4=cv:Translate(UTILS.NMToMeters(1.0),radial) -local c5=cv -vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2()} -else -local c1=cv:Translate(UTILS.NMToMeters(0.5),radial-90) -local c2=c1:Translate(UTILS.NMToMeters(0.5),radial) -local c3=cv:Translate(UTILS.NMToMeters(1.2),radial-90):Translate(UTILS.NMToMeters(3),radial) -local c4=cv:Translate(UTILS.NMToMeters(1.2),radial+90):Translate(UTILS.NMToMeters(3),radial) -local c5=cv:Translate(UTILS.NMToMeters(0.5),radial) -local c6=cv -vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2(),c6:GetVec2()} -end -self.zoneInitial:UpdateFromVec2(vec2) -return self.zoneInitial -end -function AIRBOSS:_GetZoneLineup() -self.zoneLineup=self.zoneLineup or ZONE_POLYGON_BASE:New("Zone Lineup") -local fbi=self:GetRadial(1,false,false) -local st=self:_GetOptLandingCoordinate() -local c1=st -local c2=st:Translate(UTILS.NMToMeters(0.50),fbi+15) -local c3=st:Translate(UTILS.NMToMeters(0.50),fbi+self.lue._max-0.05) -local c4=st:Translate(UTILS.NMToMeters(0.77),fbi+self.lue._max-0.05) -local c5=c4:Translate(UTILS.NMToMeters(0.25),fbi-90) -local vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2()} -self.zoneLineup:UpdateFromVec2(vec2) -return self.zoneLineup -end -function AIRBOSS:_GetZoneGroove(l,w,b) -self.zoneGroove=self.zoneGroove or ZONE_POLYGON_BASE:New("Zone Groove") -l=l or 1.50 -w=w or 0.25 -b=b or 0.10 -local fbi=self:GetRadial(1,false,false) -local st=self:_GetSternCoord() -local c1=st:Translate(self.carrierparam.totwidthstarboard,fbi-90) -local c2=st:Translate(UTILS.NMToMeters(0.10),fbi-90):Translate(UTILS.NMToMeters(0.3),fbi) -local c3=st:Translate(UTILS.NMToMeters(0.25),fbi-90):Translate(UTILS.NMToMeters(l),fbi) -local c4=st:Translate(UTILS.NMToMeters(w/2),fbi+90):Translate(UTILS.NMToMeters(l),fbi) -local c5=st:Translate(UTILS.NMToMeters(b),fbi+90):Translate(UTILS.NMToMeters(0.3),fbi) -local c6=st:Translate(self.carrierparam.totwidthport,fbi+90) -local vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2(),c6:GetVec2()} -self.zoneGroove:UpdateFromVec2(vec2) -return self.zoneGroove -end -function AIRBOSS:_GetZoneBullseye(case) -local radius=UTILS.NMToMeters(1) -local distance=UTILS.NMToMeters(3) -local radial=self:GetRadial(case,false,false) -local coord=self:GetCoordinate():Translate(distance,radial) -local vec2=coord:GetVec2() -local zone=ZONE_RADIUS:New("Zone Bullseye",vec2,radius) -return zone -end -function AIRBOSS:_GetZoneDirtyUp(case) -local radius=UTILS.NMToMeters(1) -local distance=UTILS.NMToMeters(9) -local radial=self:GetRadial(case,false,false) -local coord=self:GetCoordinate():Translate(distance,radial) -local vec2=coord:GetVec2() -local zone=ZONE_RADIUS:New("Zone Dirty Up",vec2,radius) -return zone -end -function AIRBOSS:_GetZoneArcOut(case) -local radius=UTILS.NMToMeters(1.25) -local distance=UTILS.NMToMeters(11.75) -local radial=self:GetRadial(case,false,false) -local coord=self:GetCoordinate():Translate(distance,radial) -local zone=ZONE_RADIUS:New("Zone Arc Out",coord:GetVec2(),radius) -return zone -end -function AIRBOSS:_GetZoneArcIn(case) -local radius=UTILS.NMToMeters(1.25) -local radial=self:GetRadial(case,false,true) -local alpha=math.rad(self.holdingoffset) -local x=14 -local distance=UTILS.NMToMeters(x) -local coord=self:GetCoordinate():Translate(distance,radial) -local zone=ZONE_RADIUS:New("Zone Arc In",coord:GetVec2(),radius) -return zone -end -function AIRBOSS:_GetZonePlatform(case) -local radius=UTILS.NMToMeters(1) -local radial=self:GetRadial(case,false,true) -local alpha=math.rad(self.holdingoffset) -local distance=UTILS.NMToMeters(19) -local coord=self:GetCoordinate():Translate(distance,radial) -local zone=ZONE_RADIUS:New("Zone Platform",coord:GetVec2(),radius) -return zone -end -function AIRBOSS:_GetZoneCorridor(case,l) -l=l or 31 -local radial=self:GetRadial(case,false,false) -local offset=self:GetRadial(case,false,true) -local dx=5 -local w=2 -local w2=w/2 -local d=12 -local cv=self:GetCoordinate() -local c={} -c[1]=cv:Translate(-UTILS.NMToMeters(dx),radial) -if math.abs(self.holdingoffset)>=5 then -c[2]=c[1]:Translate(UTILS.NMToMeters(w2),radial-90) -c[3]=c[2]:Translate(UTILS.NMToMeters(d+dx+w2),radial) -c[4]=cv:Translate(UTILS.NMToMeters(15),offset):Translate(UTILS.NMToMeters(1),offset-90) -c[5]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset-90) -c[6]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset+90) -c[7]=cv:Translate(UTILS.NMToMeters(13),offset):Translate(UTILS.NMToMeters(1),offset+90) -c[8]=cv:Translate(UTILS.NMToMeters(11),radial):Translate(UTILS.NMToMeters(1),radial+90) -c[9]=c[1]:Translate(UTILS.NMToMeters(w2),radial+90) -else -c[2]=c[1]:Translate(UTILS.NMToMeters(w2),radial-90) -c[3]=c[2]:Translate(UTILS.NMToMeters(dx+l),radial) -c[4]=c[3]:Translate(UTILS.NMToMeters(w),radial+90) -c[5]=c[1]:Translate(UTILS.NMToMeters(w2),radial+90) -end -local p={} -for _i,_c in ipairs(c)do -if self.Debug then -end -p[_i]=_c:GetVec2() -end -local zone=ZONE_POLYGON_BASE:New("CASE II/III Approach Corridor",p) -return zone -end -function AIRBOSS:_GetZoneCarrierBox() -self.zoneCarrierbox=self.zoneCarrierbox or ZONE_POLYGON_BASE:New("Carrier Box Zone") -local S=self:_GetSternCoord() -local hdg=self:GetHeading(false) -local p={} -p[1]=S:Translate(self.carrierparam.totwidthstarboard,hdg+90) -p[2]=p[1]:Translate(self.carrierparam.totlength,hdg) -p[3]=p[2]:Translate(self.carrierparam.totwidthstarboard+self.carrierparam.totwidthport,hdg-90) -p[4]=p[3]:Translate(self.carrierparam.totlength,hdg-180) -local vec2={} -for _,coord in ipairs(p)do -table.insert(vec2,coord:GetVec2()) -end -self.zoneCarrierbox:UpdateFromVec2(vec2) -return self.zoneCarrierbox -end -function AIRBOSS:_GetZoneRunwayBox() -self.zoneRunwaybox=self.zoneRunwaybox or ZONE_POLYGON_BASE:New("Landing Runway Zone") -local S=self:_GetSternCoord() -local FB=self:GetFinalBearing(false) -local p={} -p[1]=S:Translate(self.carrierparam.rwywidth*0.5,FB+90) -p[2]=p[1]:Translate(self.carrierparam.rwylength,FB) -p[3]=p[2]:Translate(self.carrierparam.rwywidth,FB-90) -p[4]=p[3]:Translate(self.carrierparam.rwylength,FB-180) -local vec2={} -for _,coord in ipairs(p)do -table.insert(vec2,coord:GetVec2()) -end -self.zoneRunwaybox:UpdateFromVec2(vec2) -return self.zoneRunwaybox -end -function AIRBOSS:_GetZoneAbeamLandingSpot() -local S=self:_GetOptLandingCoordinate() -local FB=self:GetFinalBearing(false) -local p={} -p[1]=S:Translate(15,FB):Translate(15,FB+90) -p[2]=S:Translate(-45,FB):Translate(15,FB+90) -p[3]=S:Translate(-45,FB):Translate(15,FB-90) -p[4]=S:Translate(15,FB):Translate(15,FB-90) -local vec2={} -for _,coord in ipairs(p)do -table.insert(vec2,coord:GetVec2()) -end -local zone=ZONE_POLYGON_BASE:New("Abeam Landing Spot Zone",vec2) -return zone -end -function AIRBOSS:_GetZoneLandingSpot() -local S=self:_GetLandingSpotCoordinate() -local FB=self:GetFinalBearing(false) -local p={} -p[1]=S:Translate(10,FB):Translate(10,FB+90) -p[2]=S:Translate(-10,FB):Translate(10,FB+90) -p[3]=S:Translate(-10,FB):Translate(10,FB-90) -p[4]=S:Translate(10,FB):Translate(10,FB-90) -local vec2={} -for _,coord in ipairs(p)do -table.insert(vec2,coord:GetVec2()) -end -local zone=ZONE_POLYGON_BASE:New("Landing Spot Zone",vec2) -return zone -end -function AIRBOSS:_GetZoneHolding(case,stack) -local zoneHolding=nil -if stack<=0 then -self:E(self.lid.."ERROR: Stack <= 0 in _GetZoneHolding!") -self:E({case=case,stack=stack}) -return nil -end -local patternalt,c1,c2=self:_GetMarshalAltitude(stack,case) -if case==1 then -local hdg=self:GetHeading() -local D=UTILS.NMToMeters(2.5) -local Post=self:GetCoordinate():Translate(D,hdg+270) -self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone",Post:GetVec2(),self.marshalradius) -if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone",self.carrier:GetVec2(),UTILS.NMToMeters(5)) -end -else -local radial=self:GetRadial(case,false,true) -local p={} -p[1]=c2:Translate(UTILS.NMToMeters(1),radial-90):GetVec2() -p[2]=c1:Translate(UTILS.NMToMeters(1),radial-90):GetVec2() -p[3]=c1:Translate(UTILS.NMToMeters(7),radial+90):GetVec2() -p[4]=c2:Translate(UTILS.NMToMeters(7),radial+90):GetVec2() -self.zoneHolding=self.zoneHolding or ZONE_POLYGON_BASE:New("CASE II/III Holding Zone") -self.zoneHolding:UpdateFromVec2(p) -end -return self.zoneHolding -end -function AIRBOSS:_GetZoneCommence(case,stack) -local zone -if case==1 then -local hdg=self:GetHeading() -local D=UTILS.NMToMeters(4.75) -local R=UTILS.NMToMeters(1) -local Three=self:GetCoordinate():Translate(D,hdg+275) -if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -local Dx=UTILS.NMToMeters(2.25) -local Dz=UTILS.NMToMeters(2.25) -R=UTILS.NMToMeters(1) -Three=self:GetCoordinate():Translate(Dz,hdg-90):Translate(Dx,hdg-180) -end -self.zoneCommence=self.zoneCommence or ZONE_RADIUS:New("CASE I Commence Zone") -self.zoneCommence:UpdateFromVec2(Three:GetVec2(),R) -else -stack=stack or 1 -local l=20+stack -local offset=self:GetRadial(case,false,true) -local cv=self:GetCoordinate() -local c={} -c[1]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset-90) -c[2]=cv:Translate(UTILS.NMToMeters(l+2.5),offset):Translate(UTILS.NMToMeters(1),offset-90) -c[3]=cv:Translate(UTILS.NMToMeters(l+2.5),offset):Translate(UTILS.NMToMeters(1),offset+90) -c[4]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset+90) -local p={} -for _i,_c in ipairs(c)do -p[_i]=_c:GetVec2() -end -self.zoneCommence=self.zoneCommence or ZONE_POLYGON_BASE:New("CASE II/III Commence Zone") -self.zoneCommence:UpdateFromVec2(p) -end -return self.zoneCommence -end -function AIRBOSS:_AttitudeMonitor(playerData) -local unit=playerData.unit -local aoa=unit:GetAoA() -local yaw=unit:GetYaw() -local roll=unit:GetRoll() -local pitch=unit:GetPitch() -local dist=playerData.unit:GetCoordinate():Get2DDistance(self:GetCoordinate()) -local dx,dz,rho,phi=self:_GetDistances(unit) -local wind=unit:GetCoordinate():GetWindWithTurbulenceVec3() -local velo=unit:GetVelocityVec3() -local vabs=UTILS.VecNorm(velo) -local rwy=false -local step=playerData.step -if playerData.step==AIRBOSS.PatternStep.FINAL or -playerData.step==AIRBOSS.PatternStep.GROOVE_XX or -playerData.step==AIRBOSS.PatternStep.GROOVE_IM or -playerData.step==AIRBOSS.PatternStep.GROOVE_IC or -playerData.step==AIRBOSS.PatternStep.GROOVE_AR or -playerData.step==AIRBOSS.PatternStep.GROOVE_AL or -playerData.step==AIRBOSS.PatternStep.GROOVE_LC or -playerData.step==AIRBOSS.PatternStep.GROOVE_IW then -step=self:_GS(step,-1) -rwy=true -end -local relhead=self:_GetRelativeHeading(playerData.unit,rwy) -local text=string.format("Pattern step: %s",step) -text=text..string.format("\nAoA=%.1f° = %.1f Units | |V|=%.1f knots",aoa,self:_AoADeg2Units(playerData,aoa),UTILS.MpsToKnots(vabs)) -if self.Debug then -text=text..string.format("\nVx=%.1f Vy=%.1f Vz=%.1f m/s",velo.x,velo.y,velo.z) -text=text..string.format("\nWind Vx=%.1f Vy=%.1f Vz=%.1f m/s",wind.x,wind.y,wind.z) -end -text=text..string.format("\nPitch=%.1f° | Roll=%.1f° | Yaw=%.1f°",pitch,roll,yaw) -text=text..string.format("\nClimb Angle=%.1f° | Rate=%d ft/min",unit:GetClimbAngle(),velo.y*196.85) -local dist=self:_GetOptLandingCoordinate():Get3DDistance(playerData.unit:GetVec3()) -local vplayer=playerData.unit:GetVelocityKMH() -local vcarrier=self.carrier:GetVelocityKMH() -local dv=math.abs(vplayer-vcarrier) -local alt=self:_GetAltCarrier(playerData.unit) -text=text..string.format("\nDist=%.1f m Alt=%.1f m delta|V|=%.1f km/h",dist,alt,dv) -if playerData.step==AIRBOSS.PatternStep.FINAL or -playerData.step==AIRBOSS.PatternStep.GROOVE_XX or -playerData.step==AIRBOSS.PatternStep.GROOVE_IM or -playerData.step==AIRBOSS.PatternStep.GROOVE_IC or -playerData.step==AIRBOSS.PatternStep.GROOVE_AR or -playerData.step==AIRBOSS.PatternStep.GROOVE_AL or -playerData.step==AIRBOSS.PatternStep.GROOVE_LC or -playerData.step==AIRBOSS.PatternStep.GROOVE_IW then -local lue=self:_Lineup(playerData.unit,true) -local gle=self:_Glideslope(playerData.unit) -text=text..string.format("\nGamma=%.1f° | Rho=%.1f°",relhead,phi) -text=text..string.format("\nLineUp=%.2f° | GlideSlope=%.2f° | AoA=%.1f Units",lue,gle,self:_AoADeg2Units(playerData,aoa)) -local grade,points,analysis=self:_LSOgrade(playerData) -text=text..string.format("\nTgroove=%.1f sec",self:_GetTimeInGroove(playerData)) -text=text..string.format("\nGrade: %s %.1f PT - %s",grade,points,analysis) -else -text=text..string.format("\nR=%.2f NM | X=%d Z=%d m",UTILS.MetersToNM(rho),dx,dz) -text=text..string.format("\nGamma=%.1f° | Rho=%.1f°",relhead,phi) -end -MESSAGE:New(text,1,nil,true):ToClient(playerData.client) -end -function AIRBOSS:_Glideslope(unit,optangle) -if optangle==nil then -if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then -optangle=3.0 -else -optangle=3.5 -end -end -local landingcoord=self:_GetOptLandingCoordinate() -local x=unit:GetCoordinate():Get2DDistance(landingcoord) -local h=self:_GetAltCarrier(unit) -if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then -h=unit:GetAltitude()-(UTILS.FeetToMeters(50)+self.carrierparam.deckheight+2) -end -local glideslope=math.atan(h/x) -local gs=math.deg(glideslope)-optangle -return gs -end -function AIRBOSS:_Glideslope2(unit,optangle) -if optangle==nil then -if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then -optangle=3.0 -else -optangle=3.5 -end -end -local landingcoord=self:_GetOptLandingCoordinate() -local x=unit:GetCoordinate():Get3DDistance(landingcoord) -local h=self:_GetAltCarrier(unit) -if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then -h=unit:GetAltitude()-(UTILS.FeetToMeters(50)+self.carrierparam.deckheight+2) -end -local glideslope=math.asin(h/x) -local gs=math.deg(glideslope)-optangle -self:T3(self.lid..string.format("Glide slope error = %.1f, x=%.1f h=%.1f",gs,x,h)) -return gs -end -function AIRBOSS:_Lineup(unit,runway) -local landingcoord=self:_GetOptLandingCoordinate() -local A=landingcoord:GetVec3() -local B=unit:GetVec3() -local C=UTILS.VecSubstract(A,B) -C.y=0.0 -local X=self.carrier:GetOrientationX() -X.y=0.0 -if runway then -X=UTILS.Rotate2D(X,-self.carrierparam.rwyangle) -end -local x=UTILS.VecDot(X,C) -local Z=self.carrier:GetOrientationZ() -Z.y=0.0 -if runway then -Z=UTILS.Rotate2D(Z,-self.carrierparam.rwyangle) -end -local z=UTILS.VecDot(Z,C) -local lineup=math.deg(math.atan2(z,x)) -return lineup -end -function AIRBOSS:_GetAltCarrier(unit) -local h=unit:GetAltitude()-self.carrierparam.deckheight-2 -return h -end -function AIRBOSS:_GetOptLandingCoordinate() -self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord()) -local FB=self:GetFinalBearing(false) -local case=self.case -if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -if case==3 then -self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()) -self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) -elseif case==2 or case==1 then -self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35,FB-90,true,true) -self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) -end -else -if self.carrierparam.wire3 then -self.landingcoord:Translate(self.carrierparam.wire3,FB,true,true) -end -self.landingcoord.y=self.landingcoord.y+2 -end -return self.landingcoord -end -function AIRBOSS:_GetLandingSpotCoordinate() -self.landingspotcoord:UpdateFromCoordinate(self:_GetSternCoord()) -local hdg=self:GetHeading() -self.landingspotcoord:Translate(self.carrierparam.landingspot,hdg,true,true):SetAltitude(self.carrierparam.deckheight) -return self.landingspotcoord -end -function AIRBOSS:GetHeading(magnetic) -self:F3({magnetic=magnetic}) -local hdg=self.carrier:GetHeading() -if magnetic then -hdg=hdg-self.magvar -end -if hdg<0 then -hdg=hdg+360 -end -return hdg -end -function AIRBOSS:GetBRC() -return self:GetHeading(true) -end -function AIRBOSS:GetWind(alt,magnetic,coord) -local cv=coord or self:GetCoordinate() -local Wdir,Wspeed=cv:GetWind(alt or 18) -if magnetic then -Wdir=Wdir-self.magvar -if Wdir<0 then -Wdir=Wdir+360 -end -end -return Wdir,Wspeed -end -function AIRBOSS:GetWindOnDeck(alt) -local cv=self:GetCoordinate() -local vc=self.carrier:GetVelocityVec3() -local xc=self.carrier:GetOrientationX() -local zc=self.carrier:GetOrientationZ() -xc=UTILS.Rotate2D(xc,-self.carrierparam.rwyangle) -zc=UTILS.Rotate2D(zc,-self.carrierparam.rwyangle) -local vw=cv:GetWindWithTurbulenceVec3(alt or 18) -local vT=UTILS.VecSubstract(vw,vc) -local vpa=UTILS.VecDot(vT,xc) -local vpp=UTILS.VecDot(vT,zc) -local vabs=UTILS.VecNorm(vT) -return-vpa,vpp,vabs -end -function AIRBOSS:GetHeadingIntoWind_old(magnetic,coord) -local function adjustDegreesForWindSpeed(windSpeed) -local degreesAdjustment=0 -if windSpeed>0 and windSpeed<3 then -degreesAdjustment=30 -elseif windSpeed>=3 and windSpeed<5 then -degreesAdjustment=20 -elseif windSpeed>=5 and windSpeed<8 then -degreesAdjustment=8 -elseif windSpeed>=8 and windSpeed<13 then -degreesAdjustment=4 -elseif windSpeed>=13 then -degreesAdjustment=0 -end -return degreesAdjustment -end -local windfrom,vwind=self:GetWind(nil,nil,coord) -local intowind=windfrom-self.carrierparam.rwyangle+adjustDegreesForWindSpeed(vwind) -if vwind<0.1 then -intowind=self:GetHeading() -end -if magnetic then -intowind=intowind-self.magvar -end -if intowind<0 then -intowind=intowind+360 -end -return intowind -end -function AIRBOSS:GetHeadingIntoWind(vdeck,magnetic,coord) -local Offset=self.carrierparam.rwyangle or 0 -local windfrom,vwind=self:GetWind(18,nil,coord) -local Vmin=4 -local Vmax=UTILS.KmphToKnots(self.carrier:GetSpeedMax()) -if vwind<0.1 then -local h=self:GetHeading(magnetic) -return h,math.min(vdeck,Vmax) -end -vwind=UTILS.MpsToKnots(vwind) -local windto=(windfrom+180)%360 -local alpha=math.rad(-Offset) -local C=math.sqrt(math.cos(alpha)^2/math.sin(alpha)^2+1) -local vdeckMax=vwind+math.cos(alpha)*Vmax -local vdeckMin=vwind+math.cos(alpha)*Vmin -local v=0 -local theta=0 -if vdeck>vdeckMax then -v=Vmax -theta=math.asin(v/(vwind*C))-math.asin(-1/C) -elseif vdeckvwind then -theta=math.pi/2 -v=math.sqrt(vdeck^2-vwind^2) -else -theta=math.asin(vdeck*math.sin(alpha)/vwind) -v=vdeck*math.cos(alpha)-vwind*math.cos(theta) -end -local magvar=magnetic and self.magvar or 0 -local intowind=(540+(windto-magvar+math.deg(theta)))%360 -return intowind,v -end -function AIRBOSS:GetBRCintoWind(vdeck) -return self:GetHeadingIntoWind(vdeck,true) -end -function AIRBOSS:GetFinalBearing(magnetic) -local fb=self:GetHeading(magnetic) -fb=fb+self.carrierparam.rwyangle -if fb<0 then -fb=fb+360 -end -return fb -end -function AIRBOSS:GetRadial(case,magnetic,offset,inverse) -case=case or self.case -local radial -if case==1 then -radial=self:GetFinalBearing(magnetic)-180 -elseif case==2 then -radial=self:GetHeading(magnetic)-180 -if offset then -radial=radial+self.holdingoffset -end -elseif case==3 then -radial=self:GetFinalBearing(magnetic)-180 -if offset then -radial=radial+self.holdingoffset -end -end -if radial<0 then -radial=radial+360 -end -if inverse then -radial=radial-180 -if radial<0 then -radial=radial+360 -end -end -return radial -end -function AIRBOSS:_GetDeltaHeading(hdg1,hdg2) -local V={} -V.x=math.cos(math.rad(hdg1)) -V.y=0 -V.z=math.sin(math.rad(hdg1)) -local W={} -W.x=math.cos(math.rad(hdg2)) -W.y=0 -W.z=math.sin(math.rad(hdg2)) -local alpha=UTILS.VecAngle(V,W) -return alpha -end -function AIRBOSS:_GetRelativeHeading(unit,runway) -local vC=self.carrier:GetOrientationX() -if runway then -vC=UTILS.Rotate2D(vC,-self.carrierparam.rwyangle) -end -local vP=unit:GetOrientationX() -vC.y=0; -vP.y=0 -local rhdg=UTILS.VecAngle(vC,vP) -return rhdg -end -function AIRBOSS:_GetRelativeVelocity(unit) -local vC=self.carrier:GetVelocityVec3() -local vP=unit:GetVelocityVec3() -vC.y=0; -vP.y=0 -local v=UTILS.VecSubstract(vP,vC) -return UTILS.VecNorm(v),v -end -function AIRBOSS:_GetDistances(unit) -local a=self.carrier:GetVec3() -local b=unit:GetVec3() -local c={x=b.x-a.x,y=0,z=b.z-a.z} -local x=self.carrier:GetOrientationX() -local dx=UTILS.VecDot(x,c) -local z=self.carrier:GetOrientationZ() -local dz=UTILS.VecDot(z,c) -local rho=math.sqrt(dx*dx+dz*dz) -local phi=math.deg(math.atan2(dz,dx)) -if phi<0 then -phi=phi+360 -end -return dx,dz,rho,phi -end -function AIRBOSS:_CheckLimits(X,Z,check) -local nextXmin=check.LimitXmin==nil or(check.LimitXmin and(check.LimitXmin<0 and X<=check.LimitXmin or check.LimitXmin>=0 and X>=check.LimitXmin)) -local nextXmax=check.LimitXmax==nil or(check.LimitXmax and(check.LimitXmax<0 and X>=check.LimitXmax or check.LimitXmax>=0 and X<=check.LimitXmax)) -local nextZmin=check.LimitZmin==nil or(check.LimitZmin and(check.LimitZmin<0 and Z<=check.LimitZmin or check.LimitZmin>=0 and Z>=check.LimitZmin)) -local nextZmax=check.LimitZmax==nil or(check.LimitZmax and(check.LimitZmax<0 and Z>=check.LimitZmax or check.LimitZmax>=0 and Z<=check.LimitZmax)) -local next=nextXmin and nextXmax and nextZmin and nextZmax -local text=string.format("step=%s: next=%s: X=%d Xmin=%s Xmax=%s | Z=%d Zmin=%s Zmax=%s",check.name,tostring(next),X,tostring(check.LimitXmin),tostring(check.LimitXmax),Z,tostring(check.LimitZmin),tostring(check.LimitZmax)) -self:T3(self.lid..text) -return next -end -function AIRBOSS:_LSOadvice(playerData,glideslopeError,lineupError) -local advice=0 -if glideslopeError>self.gle.HIGH then -self:RadioTransmission(self.LSORadio,self.LSOCall.HIGH,true,nil,nil,true) -advice=advice+self.LSOCall.HIGH.duration -elseif glideslopeError>self.gle.High then -self:RadioTransmission(self.LSORadio,self.LSOCall.HIGH,false,nil,nil,true) -advice=advice+self.LSOCall.HIGH.duration -elseif glideslopeErrorself.lue.RIGHT then -self:RadioTransmission(self.LSORadio,self.LSOCall.RIGHTFORLINEUP,true,nil,nil,true) -advice=advice+self.LSOCall.RIGHTFORLINEUP.duration -elseif lineupError>self.lue.Right then -self:RadioTransmission(self.LSORadio,self.LSOCall.RIGHTFORLINEUP,false,nil,nil,true) -advice=advice+self.LSOCall.RIGHTFORLINEUP.duration -else -end -local AOA=playerData.unit:GetAoA() -local acaoa=self:_GetAircraftAoA(playerData) -if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then -if AOA>acaoa.SLOW then -self:RadioTransmission(self.LSORadio,self.LSOCall.SLOW,true,nil,nil,true) -advice=advice+self.LSOCall.SLOW.duration -elseif AOA>acaoa.Slow then -self:RadioTransmission(self.LSORadio,self.LSOCall.SLOW,false,nil,nil,true) -advice=advice+self.LSOCall.SLOW.duration -elseif AOA>acaoa.OnSpeedMax then -elseif AOA=76 then -grade="SLOW V/STOL Groove" -else -grade="LIG" -end -if t>=16.4 and t<=16.6 then -grade="_OK_" -end -if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and(t>=60.0 and t<=65.0)then -grade="_OK_ V/STOL" -end -return grade -end -function AIRBOSS:_LSOgrade(playerData) -local function count(base,pattern) -return select(2,string.gsub(base,pattern,"")) -end -local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX) -local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM) -local GIC,nIC=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IC) -local GAR,nAR=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.AR) -local vtol=playerData.actype==AIRBOSS.AircraftCarrier.AV8B -local G=GXX.." "..GIM.." ".." "..GIC.." "..GAR -local N=nXX+nIM+nIC+nAR -local nL=count(G,'_')/2 -local nS=count(G,'%(') -local nN=N-nS-nL -local Tgroove=playerData.Tgroove -local TgrooveUnicorn=Tgroove and(Tgroove>=15.0 and Tgroove<=18.99)or false -local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false -local grade -local points -if N==0 and(TgrooveUnicorn or TgrooveVstolUnicorn or playerData.case==3)then -grade="_OK_" -points=5.0 -G="Unicorn" -else -if vtol then -local Gb=GXX.." "..GIM -local N=nXX+nIM -local nL=count(Gb,'_')/2 -local nS=count(Gb,'%(') -local nN=N-nS-nL -local Gv=GIC.." "..GAR -local Nv=nIC+nAR -local nLv=count(Gv,'_')/2 -local nSv=count(Gv,'%(') -local nNv=Nv-nSv-nLv -if nL>0 or nLv>1 then -grade="--" -points=2.0 -elseif nN>0 or nNv>1 or nLv==1 then -grade="(OK)" -points=3.0 -else -grade="OK" -points=4.0 -end -else -if nL>0 then -grade="--" -points=2.0 -elseif nN>0 then -grade="(OK)" -points=3.0 -else -grade="OK" -points=4.0 -end -end -end -G=G:gsub("%)%(","") -G=G:gsub("__","") -local text="LSO grade:\n" -text=text..G.."\n" -text=text.."Grade = "..grade.." points = "..points.."\n" -text=text.."# of total deviations = "..N.."\n" -text=text.."# of large deviations _ = "..nL.."\n" -text=text.."# of normal deviations = "..nN.."\n" -text=text.."# of small deviations ( = "..nS.."\n" -self:T2(self.lid..text) -if playerData.wop then -if playerData.lig then -grade="WO" -points=1.0 -G="LIG" -else -grade="WOP" -points=2.0 -G="n/a" -end -elseif playerData.wofd then -if playerData.landed then -grade="CUT" -points=0.0 -else -grade="WOFD" -points=-1.0 -end -G="n/a" -elseif playerData.owo then -grade="OWO" -points=2.0 -if N==0 then -G="n/a" -end -elseif playerData.waveoff then -if playerData.landed then -grade="CUT" -points=0.0 -else -grade="WO" -points=1.0 -end -elseif playerData.boltered then -grade="-- (BOLTER)" -points=2.5 -elseif not playerData.hover and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then -if playerData.landed then -grade="CUT" -points=0.0 -end -end -return grade,points,G -end -function AIRBOSS:_Flightdata2Text(playerData,groovestep) -local function little(text) -return string.format("(%s)",text) -end -local function underline(text) -return string.format("_%s_",text) -end -local fdata=playerData.groove[groovestep] -if fdata==nil then -self:T3(self.lid.."Flight data is nil.") -return"",0 -end -local step=fdata.Step -local AOA=fdata.AoA -local GSE=fdata.GSE -local LUE=fdata.LUE -local ROL=fdata.Roll -local acaoa=self:_GetAircraftAoA(playerData) -local P=nil -if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=4.0 and playerData.case<3 then -if LUE>self.lue.RIGHT then -P=underline("AA") -elseif LUE>self.lue.RightMed then -P="AA " -elseif LUE>self.lue.Right then -P=little("AA") -end -end -local O=nil -if step==AIRBOSS.PatternStep.GROOVE_XX then -if LUEacaoa.SLOW then -S=underline("SLO") -elseif AOA>acaoa.Slow then -S="SLO" -elseif AOA>acaoa.OnSpeedMax then -S=little("SLO") -elseif AOAself.gle.HIGH then -A=underline("H") -elseif GSE>self.gle.High then -A="H" -elseif GSE>self.gle._max then -A=little("H") -elseif GSEself.lue.RIGHT then -D=underline("LUL") -elseif LUE>self.lue.Right then -D="LUL" -elseif LUE>self.lue._max then -D=little("LUL") -elseif playerData.case<3 then -if LUEpos.Xmax then -self:T(string.format("Xmax: X=%d > %d=Xmax",X,pos.Xmax)) -abort=true -elseif pos.Zmin and Zpos.Zmax then -self:T(string.format("Zmax: Z=%d > %d=Zmax",Z,pos.Zmax)) -abort=true -end -return abort -end -function AIRBOSS:_TooFarOutText(X,Z,posData) -local text="you are too " -local xtext=nil -if posData.Xmin and XposData.Xmax then -if posData.Xmax>=0 then -xtext="far ahead of " -else -xtext="close to " -end -end -local ztext=nil -if posData.Zmin and ZposData.Zmax then -if posData.Zmax>=0 then -ztext="far starboard of " -else -ztext="too close to " -end -end -if xtext and ztext then -text=text..xtext.." and "..ztext -elseif xtext then -text=text..xtext -elseif ztext then -text=text..ztext -end -text=text.."the carrier." -if xtext==nil and ztext==nil then -text="you are too far from where you should be!" -end -return text -end -function AIRBOSS:_AbortPattern(playerData,X,Z,posData,patternwo) -local text=self:_TooFarOutText(X,Z,posData) -local dtext=string.format("Abort: X=%d Xmin=%s, Xmax=%s | Z=%d Zmin=%s Zmax=%s",X,tostring(posData.Xmin),tostring(posData.Xmax),Z,tostring(posData.Zmin),tostring(posData.Zmax)) -self:T(self.lid..dtext) -self:MessageToPlayer(playerData,text,"LSO") -if patternwo then -playerData.wop=true -self:_AddToDebrief(playerData,string.format("Pattern wave off: %s",text)) -self:RadioTransmission(self.LSORadio,self.LSOCall.DEPARTANDREENTER,false,3,nil,nil,true) -playerData.step=AIRBOSS.PatternStep.DEBRIEF -playerData.warning=nil -end -end -function AIRBOSS:_PlayerHint(playerData,delay,soundoff) -if not playerData.showhints then -return -end -local alt,aoa,dist,speed=self:_GetAircraftParameters(playerData) -local hintAlt,debriefAlt,callAlt=self:_AltitudeCheck(playerData,alt) -local hintSpeed,debriefSpeed,callSpeed=self:_SpeedCheck(playerData,speed) -local hintAoA,debriefAoA,callAoA=self:_AoACheck(playerData,aoa) -local hintDist,debriefDist,callDist=self:_DistanceCheck(playerData,dist) -local hint="" -if hintAlt and hintAlt~=""then -hint=hint.."\n"..hintAlt -end -if hintSpeed and hintSpeed~=""then -hint=hint.."\n"..hintSpeed -end -if hintAoA and hintAoA~=""then -hint=hint.."\n"..hintAoA -end -if hintDist and hintDist~=""then -hint=hint.."\n"..hintDist -end -local debrief="" -if debriefAlt and debriefAlt~=""then -debrief=debrief.."\n- "..debriefAlt -end -if debriefSpeed and debriefSpeed~=""then -debrief=debrief.."\n- "..debriefSpeed -end -if debriefAoA and debriefAoA~=""then -debrief=debrief.."\n- "..debriefAoA -end -if debriefDist and debriefDist~=""then -debrief=debrief.."\n- "..debriefDist -end -if debrief~=""then -self:_AddToDebrief(playerData,debrief) -end -delay=delay or 0 -if not soundoff then -if callAlt then -self:Sound2Player(playerData,self.LSORadio,callAlt,false,delay) -delay=delay+callAlt.duration+0.5 -end -if callSpeed then -self:Sound2Player(playerData,self.LSORadio,callSpeed,false,delay) -delay=delay+callSpeed.duration+0.5 -end -if callAoA then -self:Sound2Player(playerData,self.LSORadio,callAoA,false,delay) -delay=delay+callAoA.duration+0.5 -end -if callDist then -self:Sound2Player(playerData,self.LSORadio,callDist,false,delay) -delay=delay+callDist.duration+0.5 -end -end -if playerData.step==AIRBOSS.PatternStep.ARCIN then -if playerData.difficulty==AIRBOSS.Difficulty.EASY then -local radial=self:GetRadial(playerData.case,true,false,true) -local turn="right" -if self.holdingoffset<0 then -turn="left" -end -hint=hint..string.format("\nTurn %s and select TACAN %03d°.",turn,radial) -end -end -if playerData.step==AIRBOSS.PatternStep.DIRTYUP then -if playerData.difficulty==AIRBOSS.Difficulty.EASY then -if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then -hint=hint.."\nFAF! Checks completed. Nozzles 50°." -else -hint=hint.."\nDirty up! Hook, gear and flaps down." -end -end -end -if playerData.step==AIRBOSS.PatternStep.BULLSEYE then -if playerData.difficulty==AIRBOSS.Difficulty.EASY then -if playerData.actype==AIRBOSS.AircraftCarrier.HORNET -or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE -or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF -or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER then -hint=hint..string.format("\nIntercept glideslope and follow the needles.") -else -hint=hint..string.format("\nIntercept glideslope.") -end -end -end -if hint~=""then -local text=string.format("%s%s",playerData.step,hint) -self:MessageToPlayer(playerData,hint,"AIRBOSS","") -end -end -function AIRBOSS:_StepHint(playerData,step) -step=step or playerData.step -if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then -local alt,aoa,dist,speed=self:_GetAircraftParameters(playerData,step) -local hint="" -if alt then -hint=hint..string.format("\nAltitude %d ft",UTILS.MetersToFeet(alt)) -end -if aoa then -hint=hint..string.format("\nAoA %.1f",self:_AoADeg2Units(playerData,aoa)) -end -if speed then -hint=hint..string.format("\nSpeed %d knots",UTILS.MpsToKnots(speed)) -end -if dist then -hint=hint..string.format("\nDistance to the boat %.1f NM",UTILS.MetersToNM(dist)) -end -if step==AIRBOSS.PatternStep.LATEBREAK then -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then -hint=hint.."\nWing Sweep 20°, Gear DOWN < 280 KIAS." -end -end -if step==AIRBOSS.PatternStep.ABEAM then -if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then -hint=hint.."\nNozzles 50°-60°. Antiskid OFF. Lights OFF." -elseif playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then -hint=hint.."\nSlats/Flaps EXTENDED < 225 KIAS. DLC SELECTED. Auto Throttle IF DESIRED." -else -hint=hint.."\nDirty up! Gear DOWN, flaps DOWN. Check hook down." -end -end -if hint~=""then -local text=string.format("Optimal setup at next step %s:%s",step,hint) -self:MessageToPlayer(playerData,text,"AIRBOSS","",nil,false,1) -end -end -end -function AIRBOSS:_AltitudeCheck(playerData,altopt) -if altopt==nil then -return nil,nil -end -local altitude=playerData.unit:GetAltitude() -local lowscore,badscore=self:_GetGoodBadScore(playerData) -local _error=(altitude-altopt)/altopt*100 -local radiocall=nil -local hint="" -if _error>badscore then -radiocall=self:_NewRadioCall(self.LSOCall.HIGH,"Paddles","") -elseif _error>lowscore then -radiocall=self:_NewRadioCall(self.LSOCall.HIGH,"Paddles","") -elseif _error<-badscore then -radiocall=self:_NewRadioCall(self.LSOCall.LOW,"Paddles","") -elseif _error<-lowscore then -radiocall=self:_NewRadioCall(self.LSOCall.LOW,"Paddles","") -else -hint=string.format("Good altitude. ") -end -if playerData.difficulty==AIRBOSS.Difficulty.EASY then -hint=hint..string.format("Optimal altitude is %d ft.",UTILS.MetersToFeet(altopt)) -elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then -hint="" -elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then -hint="" -end -local debrief=string.format("Altitude %d ft = %d%% deviation from %d ft.",UTILS.MetersToFeet(altitude),_error,UTILS.MetersToFeet(altopt)) -return hint,debrief,radiocall -end -function AIRBOSS:_AoACheck(playerData,optaoa) -if optaoa==nil then -return nil,nil -end -local lowscore,badscore=self:_GetGoodBadScore(playerData) -local aoa=playerData.unit:GetAoA() -local _error=(aoa-optaoa)/optaoa*100 -local aircraftaoa=self:_GetAircraftAoA(playerData) -local radiocall=nil -local hint="" -if aoa>=aircraftaoa.SLOW then -radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"Paddles","") -elseif aoa>=aircraftaoa.Slow then -radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"Paddles","") -elseif aoa>=aircraftaoa.OnSpeedMax then -hint="Your're a little slow. " -elseif aoa>=aircraftaoa.OnSpeedMin then -hint="You're on speed. " -elseif aoa>=aircraftaoa.Fast then -hint="You're a little fast. " -elseif aoa>=aircraftaoa.FAST then -radiocall=self:_NewRadioCall(self.LSOCall.FAST,"Paddles","") -else -radiocall=self:_NewRadioCall(self.LSOCall.FAST,"Paddles","") -end -if playerData.difficulty==AIRBOSS.Difficulty.EASY then -hint=hint..string.format("Optimal AoA is %.1f.",self:_AoADeg2Units(playerData,optaoa)) -elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then -hint="" -elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then -hint="" -end -local debrief=string.format("AoA %.1f = %d%% deviation from %.1f.",self:_AoADeg2Units(playerData,aoa),_error,self:_AoADeg2Units(playerData,optaoa)) -return hint,debrief,radiocall -end -function AIRBOSS:_SpeedCheck(playerData,speedopt) -if speedopt==nil then -return nil,nil -end -local speed=playerData.unit:GetVelocityMPS() -local lowscore,badscore=self:_GetGoodBadScore(playerData) -local _error=(speed-speedopt)/speedopt*100 -local radiocall=nil -local hint="" -if _error>badscore then -radiocall=self:_NewRadioCall(self.LSOCall.FAST,"AIRBOSS","") -elseif _error>lowscore then -radiocall=self:_NewRadioCall(self.LSOCall.FAST,"AIRBOSS","") -elseif _error<-badscore then -radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"AIRBOSS","") -elseif _error<-lowscore then -radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"AIRBOSS","") -else -hint=string.format("Good speed. ") -end -if playerData.difficulty==AIRBOSS.Difficulty.EASY then -hint=hint..string.format("Optimal speed is %d knots.",UTILS.MpsToKnots(speedopt)) -elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then -hint="" -elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then -hint="" -end -local debrief=string.format("Speed %d knots = %d%% deviation from %d knots.",UTILS.MpsToKnots(speed),_error,UTILS.MpsToKnots(speedopt)) -return hint,debrief,radiocall -end -function AIRBOSS:_DistanceCheck(playerData,optdist) -if optdist==nil then -return nil,nil -end -local distance=playerData.unit:GetCoordinate():Get2DDistance(self:GetCoordinate()) -local lowscore,badscore=self:_GetGoodBadScore(playerData) -local _error=(distance-optdist)/optdist*100 -local hint -if _error>badscore then -hint=string.format("You're too far from the boat!") -elseif _error>lowscore then -hint=string.format("You're slightly too far from the boat.") -elseif _error<-badscore then -hint=string.format("You're too close to the boat!") -elseif _error<-lowscore then -hint=string.format("You're slightly too far from the boat.") -else -hint=string.format("Good distance to the boat.") -end -if playerData.difficulty==AIRBOSS.Difficulty.EASY then -hint=hint..string.format(" Optimal distance is %.1f NM.",UTILS.MetersToNM(optdist)) -elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then -hint="" -elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then -hint="" -end -local debrief=string.format("Distance %.1f NM = %d%% deviation from %.1f NM.",UTILS.MetersToNM(distance),_error,UTILS.MetersToNM(optdist)) -return hint,debrief,nil -end -function AIRBOSS:_AddToDebrief(playerData,hint,step) -step=step or playerData.step -table.insert(playerData.debrief,{step=step,hint=hint}) -end -function AIRBOSS:_Debrief(playerData) -self:F(self.lid..string.format("Debriefing of player %s.",playerData.name)) -playerData.debriefschedulerID=nil -playerData.attitudemonitor=false -local grade,points,analysis=self:_LSOgrade(playerData) -if points and points>=0 then -table.insert(playerData.points,points) -end -local Points=0 -if playerData.landed and not playerData.unit:InAir()then -for _,_points in pairs(playerData.points)do -Points=Points+_points -end -Points=Points/#playerData.points -playerData.points={} -else -Points=points -end -local mygrade={} -mygrade.grade=grade -mygrade.points=points -mygrade.details=analysis -mygrade.wire=playerData.wire -mygrade.Tgroove=playerData.Tgroove -if playerData.landed and not playerData.unit:InAir()then -mygrade.finalscore=Points -end -mygrade.case=playerData.case -local windondeck=self:GetWindOnDeck() -mygrade.wind=UTILS.Round(UTILS.MpsToKnots(windondeck),1) -mygrade.modex=playerData.onboard -mygrade.airframe=playerData.actype -mygrade.carriertype=self.carriertype -mygrade.carriername=self.alias -mygrade.carrierrwy=self.carrierparam.rwyangle -mygrade.theatre=self.theatre -mygrade.mitime=UTILS.SecondsToClock(timer.getAbsTime(),true) -mygrade.midate=UTILS.GetDCSMissionDate() -mygrade.osdate="n/a" -if os then -mygrade.osdate=os.date() -end -playerData.grade=mygrade -if playerData.trapon and self.trapsheet then -self:_SaveTrapSheet(playerData,mygrade) -end -table.insert(self.playerscores[playerData.name],mygrade) -self:LSOGrade(playerData,mygrade) -local text=string.format("%s %.1f PT - %s",grade,Points,analysis) -if Points==-1 then -text=string.format("%s n/a PT - Foul deck",grade,Points,analysis) -end -if not(playerData.wop or playerData.wofd)then -if playerData.wire and playerData.wire<=4 then -text=text..string.format(" %d-wire",playerData.wire) -end -if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then -text=text..string.format("\nTime in the groove %.1f seconds: %s",playerData.Tgroove,self:_EvalGrooveTime(playerData)) -end -end -playerData.lastdebrief=UTILS.DeepCopy(playerData.debrief) -if playerData.difficulty==AIRBOSS.Difficulty.EASY then -text=text..string.format("\nYour detailed debriefing can be found via the F10 radio menu.") -end -self:MessageToPlayer(playerData,text,"LSO","",30,true) -playerData.step=AIRBOSS.PatternStep.UNDEFINED -if playerData.wop then -if playerData.unit:IsAlive()then -local heading,distance -if playerData.case==1 or playerData.case==2 then -playerData.step=AIRBOSS.PatternStep.INITIAL -local initial=self:GetCoordinate():Translate(UTILS.NMToMeters(3.5),self:GetRadial(2,false,false,false)) -heading=playerData.unit:GetCoordinate():HeadingTo(initial) -distance=playerData.unit:GetCoordinate():Get2DDistance(initial) -elseif playerData.case==3 then -playerData.step=AIRBOSS.PatternStep.BULLSEYE -local zone=self:_GetZoneBullseye(playerData.case) -heading=playerData.unit:GetCoordinate():HeadingTo(zone:GetCoordinate()) -distance=playerData.unit:GetCoordinate():Get2DDistance(zone:GetCoordinate()) -end -local text=string.format("fly heading %03d° for %d NM to re-enter the pattern.",heading,UTILS.MetersToNM(distance)) -self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,5) -else -self:E(self.lid..string.format("ERROR: Player unit not alive!")) -end -elseif playerData.wofd then -if playerData.unit:InAir()then -playerData.step=AIRBOSS.PatternStep.BOLTER -else -self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD) -local text=string.format("deck was fouled but you landed anyway. Airboss wants to talk to you!") -self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3) -end -elseif playerData.owo then -if playerData.unit:InAir()then -playerData.step=AIRBOSS.PatternStep.BOLTER -else -self:E(self.lid.."ERROR: player landed when OWO was issues. This should not happen. Please report!") -self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD) -end -elseif playerData.waveoff then -if playerData.unit:InAir()then -playerData.step=AIRBOSS.PatternStep.BOLTER -else -self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD) -local text=string.format("you were waved off but landed anyway. Airboss wants to talk to you!") -self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3) -end -elseif playerData.boltered then -if playerData.unit:InAir()then -playerData.step=AIRBOSS.PatternStep.BOLTER -end -elseif playerData.landed then -if not playerData.unit:InAir()then -self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD) -end -else -self:MessageToPlayer(playerData,"Undefined state after landing! Please report.","ERROR",nil,20) -playerData.step=AIRBOSS.PatternStep.UNDEFINED -end -if playerData.landed and not playerData.unit:InAir()then -self:_RecoveredElement(playerData.unit) -self:_CheckSectionRecovered(playerData) -end -playerData.passes=playerData.passes+1 -self:_StepHint(playerData) -self:_InitPlayer(playerData,playerData.step) -MESSAGE:New(string.format("Player step %s.",playerData.step),5,"DEBUG"):ToAllIf(self.Debug) -if self.autosave and mygrade.finalscore then -self:Save(self.autosavepath,self.autosavefile) -end -end -function AIRBOSS:_CheckCollisionCoord(coordto,coordfrom) -local dx=100 -local d=0 -if coordfrom then -d=0 -else -d=250 -coordfrom=self:GetCoordinate():Translate(d,self:GetHeading()) -end -local dmax=coordfrom:Get2DDistance(coordto) -local direction=coordfrom:HeadingTo(coordto) -local clear=true -while d<=dmax do -local cp=coordfrom:Translate(d,direction) -if not cp:IsSurfaceTypeWater()then -if self.Debug then -local st=cp:GetSurfaceType() -cp:MarkToAll(string.format("Collision check surface type %d",st)) -end -clear=false -break -end -d=d+dx -end -local text="" -if clear then -text=string.format("Path into direction %03d° is clear for the next %.1f NM.",direction,UTILS.MetersToNM(d)) -else -text=string.format("Detected obstacle at distance %.1f NM into direction %03d°.",UTILS.MetersToNM(d),direction) -end -self:T2(self.lid..text) -return not clear,d -end -function AIRBOSS:_CheckFreePathToNextWP(fromcoord) -fromcoord=fromcoord or self:GetCoordinate():Translate(250,self:GetHeading()) -local Nnextwp=math.min(self.currentwp+1,#self.waypoints) -local nextwp=self.waypoints[Nnextwp] -local collision=self:_CheckCollisionCoord(nextwp,fromcoord) -return collision -end -function AIRBOSS:_Pathfinder() -local hdg=self:GetHeading() -local cv=self:GetCoordinate() -local directions={-20,20,-30,30,-40,40,-50,50,-60,60,-70,70,-80,80,-90,90,-100,100} -for _,_direction in pairs(directions)do -local direction=hdg+_direction -local _,dfree=self:_CheckCollisionCoord(cv:Translate(UTILS.NMToMeters(20),direction),cv) -local distance=500 -while distance<=dfree do -local fromcoord=cv:Translate(distance,direction) -local collision=self:_CheckFreePathToNextWP(fromcoord) -self:T2(self.lid..string.format("Pathfinder d=%.1f m, direction=%03d°, collision=%s",distance,direction,tostring(collision))) -if not collision then -self:CarrierDetour(fromcoord) -return -end -distance=distance+500 -end -end -end -function AIRBOSS:CarrierResumeRoute(gotocoord) -AIRBOSS._ResumeRoute(self.carrier:GetGroup(),self,gotocoord) -return self -end -function AIRBOSS:CarrierDetour(coord,speed,uturn,uspeed,tcoord) -local pos0=self:GetCoordinate() -local vel0=self.carrier:GetVelocityKNOTS() -speed=speed or math.max(vel0,5) -local speedkmh=math.max(UTILS.KnotsToKmph(speed),UTILS.KnotsToKmph(2)) -local cspeedkmh=math.max(self.carrier:GetVelocityKMH(),UTILS.KnotsToKmph(10)) -local uspeedkmh=UTILS.KnotsToKmph(uspeed or speed) -local wp={} -table.insert(wp,pos0:WaypointGround(cspeedkmh)) -if tcoord then -table.insert(wp,tcoord:WaypointGround(cspeedkmh)) -end -table.insert(wp,coord:WaypointGround(speedkmh)) -if uturn then -table.insert(wp,pos0:WaypointGround(uspeedkmh)) -end -local group=self.carrier:GetGroup() -local TaskResumeRoute=group:TaskFunction("AIRBOSS._ResumeRoute",self) -group:SetTaskWaypoint(wp[#wp],TaskResumeRoute) -if self.Debug then -if tcoord then -tcoord:MarkToAll(string.format("Detour Turn Help WP. Speed %.1f knots",UTILS.KmphToKnots(cspeedkmh))) -end -coord:MarkToAll(string.format("Detour Waypoint. Speed %.1f knots",UTILS.KmphToKnots(speedkmh))) -if uturn then -pos0:MarkToAll(string.format("Detour U-turn WP. Speed %.1f knots",UTILS.KmphToKnots(uspeedkmh))) -end -end -self.detour=true -self.carrier:Route(wp) -end -function AIRBOSS:CarrierTurnIntoWind(time,vdeck,uturn) -local _,vwind=self:GetWind() -local vdeck=UTILS.MpsToKnots(vdeck) -local hiw,speedknots=self:GetHeadingIntoWind(vdeck) -local vtot=UTILS.KnotsToMps(speedknots) -local dist=vtot*time -local distNM=UTILS.MetersToNM(dist) -local hdg=self:GetHeading() -local deltaH=self:_GetDeltaHeading(hdg,hiw) -self:I(self.lid..string.format("Carrier steaming into the wind (%.1f kts). Heading=%03d-->%03d (Delta=%.1f), Speed=%.1f knots, Distance=%.1f NM, Time=%d sec", -UTILS.MpsToKnots(vwind),hdg,hiw,deltaH,speedknots,distNM,speedknots,time)) -local Cv=self:GetCoordinate() -local Ctiw=nil -local Csoo=nil -if deltaH<45 then -Csoo=Cv:Translate(750,hdg):Translate(750,hiw) -local hsw=self:GetHeadingIntoWind(vdeck,false,Csoo) -Ctiw=Csoo:Translate(dist,hsw) -elseif deltaH<90 then -Csoo=Cv:Translate(900,hdg):Translate(900,hiw) -local hsw=self:GetHeadingIntoWind(vdeck,false,Csoo) -Ctiw=Csoo:Translate(dist,hsw) -elseif deltaH<135 then -Csoo=Cv:Translate(1100,hdg-90):Translate(1000,hiw) -local hsw=self:GetHeadingIntoWind(vdeck,false,Csoo) -Ctiw=Csoo:Translate(dist,hsw) -else -Csoo=Cv:Translate(1200,hdg-90):Translate(1000,hiw) -local hsw=self:GetHeadingIntoWind(vdeck,false,Csoo) -Ctiw=Csoo:Translate(dist,hsw) -end -self.Creturnto=self:GetCoordinate() -local nextwp=self:_GetNextWaypoint() -local vdownwind=UTILS.MpsToKnots(nextwp:GetVelocity()) -if vdownwind<1 then -vdownwind=10 -end -self:CarrierDetour(Ctiw,speedknots,uturn,vdownwind,Csoo) -self.turnintowind=true -return self -end -function AIRBOSS:_GetNextWaypoint() -local Nextwp=nil -if self.currentwp==#self.waypoints then -Nextwp=1 -else -Nextwp=self.currentwp+1 -end -local text=string.format("Current WP=%d/%d, next WP=%d",self.currentwp,#self.waypoints,Nextwp) -self:T2(self.lid..text) -local nextwp=self.waypoints[Nextwp] -return nextwp,Nextwp -end -function AIRBOSS:_InitWaypoints() -local Waypoints=self.carrier:GetGroup():GetTemplateRoutePoints() -self.waypoints={} -for i,point in ipairs(Waypoints)do -local coord=COORDINATE:New(point.x,point.alt,point.y) -coord:SetVelocity(point.speed) -table.insert(self.waypoints,coord) -if self.Debug then -coord:MarkToAll(string.format("Carrier Waypoint %d, Speed=%.1f knots",i,UTILS.MpsToKnots(point.speed))) -end -end -return self -end -function AIRBOSS:_PatrolRoute(n) -local nextWP,N=self:_GetNextWaypoint() -n=n or N -local CarrierGroup=self.carrier:GetGroup() -local Waypoints={} -local wp=self:GetCoordinate():WaypointGround(CarrierGroup:GetVelocityKMH()) -table.insert(Waypoints,wp) -for i=n,#self.waypoints do -local coord=self.waypoints[i] -local wp=coord:WaypointGround(UTILS.MpsToKmph(coord.Velocity)) -local TaskPassingWP=CarrierGroup:TaskFunction("AIRBOSS._PassingWaypoint",self,i,#self.waypoints) -CarrierGroup:SetTaskWaypoint(wp,TaskPassingWP) -table.insert(Waypoints,wp) -end -CarrierGroup:Route(Waypoints) -return self -end -function AIRBOSS:_GetETAatNextWP() -local cwp=self.currentwp -local tnow=timer.getAbsTime() -local p=self:GetCoordinate() -local v=self.carrier:GetVelocityMPS() -local nextWP=self:_GetNextWaypoint() -local s=p:Get2DDistance(nextWP) -local t=s/v -local eta=t+tnow -return eta -end -function AIRBOSS:_CheckCarrierTurning() -local vNew=self.carrier:GetOrientationX() -local vLast=self.Corientlast -vNew.y=0; -vLast.y=0 -local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast))) -self.Corientlast=vNew -local turning=math.abs(deltaLast)>=1 -if self.turning and not turning then -local FB=self:GetFinalBearing(true) -self:_MarshalCallNewFinalBearing(FB) -end -if turning and not self.turning then -local hdg -if self.turnintowind then -local vdeck=self.recoverywindow and self.recoverywindow.SPEED or 20 -hdg=self:GetHeadingIntoWind(vdeck,false) -else -hdg=self:GetCoordinate():HeadingTo(self:_GetNextWaypoint()) -end -hdg=hdg-self.magvar -if hdg<0 then -hdg=360+hdg -end -self:_MarshalCallCarrierTurnTo(hdg) -end -self.turning=turning -end -function AIRBOSS:_CheckPatternUpdate() -local dTPupdate=10*60 -local Dupdate=UTILS.NMToMeters(2.5) -local Hupdate=5 -local dt=timer.getTime()-self.Tpupdate -if dt=Hupdate then -self:T(self.lid..string.format("Carrier heading changed by %d°.",deltaHeading)) -Hchange=true -end -local pos=self:GetCoordinate() -local dist=pos:Get2DDistance(self.Cposition) -local Dchange=false -if dist>=Dupdate then -self:T(self.lid..string.format("Carrier position changed by %.1f NM.",UTILS.MetersToNM(dist))) -Dchange=true -end -if Hchange or Dchange then -for _,_flight in pairs(self.Qmarshal)do -local flight=_flight -if flight.ai then -self:_MarshalAI(flight,flight.flag) -end -end -self.Corientation=vNew -self.Cposition=pos -self.Tpupdate=timer.getTime() -end -end -function AIRBOSS._PassingWaypoint(group,airboss,i,final) -local text=string.format("Group %s passing waypoint %d of %d.",group:GetName(),i,final) -if airboss.Debug and false then -local pos=group:GetCoordinate() -pos:SmokeRed() -local MarkerID=pos:MarkToAll(string.format("Group %s reached waypoint %d",group:GetName(),i)) -end -MESSAGE:New(text,10):ToAllIf(airboss.Debug) -airboss:T(airboss.lid..text) -airboss.currentwp=i -airboss:PassingWaypoint(i) -if i==final and final>1 and airboss.adinfinitum then -airboss:_PatrolRoute() -end -end -function AIRBOSS._ResumeRoute(group,airboss,gotocoord) -local nextwp,Nextwp=airboss:_GetNextWaypoint() -local speedkmh=nextwp.Velocity*3.6 -if speedkmh<1 then -speedkmh=UTILS.KnotsToKmph(10) -end -local waypoints={} -local c0=group:GetCoordinate() -local wp0=c0:WaypointGround(speedkmh) -table.insert(waypoints,wp0) -if gotocoord then -local headingto=c0:HeadingTo(gotocoord) -local hdg1=airboss:GetHeading() -local hdg2=c0:HeadingTo(gotocoord) -local delta=airboss:_GetDeltaHeading(hdg1,hdg2) -if delta>90 then -local turnradius=UTILS.NMToMeters(3) -local gotocoordh=c0:Translate(turnradius,hdg1+45) -local wp=gotocoordh:WaypointGround(speedkmh) -table.insert(waypoints,wp) -gotocoordh=c0:Translate(turnradius,hdg1+90) -wp=gotocoordh:WaypointGround(speedkmh) -table.insert(waypoints,wp) -end -local wp1=gotocoord:WaypointGround(speedkmh) -table.insert(waypoints,wp1) -end -local text=string.format("Carrier is resuming route. Next waypoint %d, Speed=%.1f knots.",Nextwp,UTILS.KmphToKnots(speedkmh)) -MESSAGE:New(text,10):ToAllIf(airboss.Debug) -airboss:I(airboss.lid..text) -for i=Nextwp,#airboss.waypoints do -local coord=airboss.waypoints[i] -local speed=coord.Velocity*3.6 -if speed<1 then -speed=UTILS.KnotsToKmph(10) -end -local wp=coord:WaypointGround(speed) -local TaskPassingWP=group:TaskFunction("AIRBOSS._PassingWaypoint",airboss,i,#airboss.waypoints) -group:SetTaskWaypoint(wp,TaskPassingWP) -table.insert(waypoints,wp) -end -airboss.turnintowind=false -airboss.detour=false -group:Route(waypoints) -end -function AIRBOSS._ReachedHoldingZone(group,airboss,flight) -local text=string.format("Flight %s reached holding zone.",group:GetName()) -MESSAGE:New(text,10):ToAllIf(airboss.Debug) -airboss:T(airboss.lid..text) -if airboss.Debug then -group:GetCoordinate():MarkToAll(text) -end -if flight then -flight.holding=true -flight.time=timer.getAbsTime() -end -end -function AIRBOSS._TaskFunctionMarshalAI(group,airboss,flight) -local text=string.format("Flight %s is send to marshal.",group:GetName()) -MESSAGE:New(text,10):ToAllIf(airboss.Debug) -airboss:T(airboss.lid..text) -local stack=airboss:_GetFreeStack(flight.ai) -if stack then -airboss:_MarshalAI(flight,stack) -else -if not airboss:_InQueue(airboss.Qwaiting,flight.group)then -airboss:_WaitAI(flight) -end -end -if flight.refueling==true then -airboss:I(airboss.lid..string.format("Flight group %s finished refueling task.",flight.groupname)) -end -flight.refueling=false -end -function AIRBOSS:_GetACNickname(actype) -local nickname="unknown" -if actype==AIRBOSS.AircraftCarrier.A4EC then -nickname="Skyhawk" -elseif actype==AIRBOSS.AircraftCarrier.T45C then -nickname="Goshawk" -elseif actype==AIRBOSS.AircraftCarrier.AV8B then -nickname="Harrier" -elseif actype==AIRBOSS.AircraftCarrier.E2D then -nickname="Hawkeye" -elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B then -nickname="Tomcat" -elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then -nickname="Hornet" -elseif actype==AIRBOSS.AircraftCarrier.RHINOE or actype==AIRBOSS.AircraftCarrier.RHINOF then -nickname="Rhino" -elseif actype==AIRBOSS.AircraftCarrier.GROWLER then -nickname="Growler" -elseif actype==AIRBOSS.AircraftCarrier.S3B or actype==AIRBOSS.AircraftCarrier.S3BTANKER then -nickname="Viking" -end -return nickname -end -function AIRBOSS:_GetOnboardNumberPlayer(group) -return self:_GetOnboardNumbers(group,true) -end -function AIRBOSS:_GetOnboardNumbers(group,playeronly) -local groupname=group:GetName() -local text=string.format("Onboard numbers of group %s:",groupname) -local units=group:GetTemplate().units -local numbers={} -for _,unit in pairs(units)do -local n=tostring(unit.onboard_num) -local name=unit.name -local skill=unit.skill or"Unknown" -text=text..string.format("\n- unit %s: onboard #=%s skill=%s",name,n,tostring(skill)) -if playeronly and skill=="Client"or skill=="Player"then -return n -end -numbers[name]=n -end -self:T2(self.lid..text) -return numbers -end -function AIRBOSS:_GetTowerFrequency() -self.TowerFreq=0 -local striketemplate=self.carrier:GetGroup():GetTemplate() -for _,unit in pairs(striketemplate.units)do -if self.carrier:GetName()==unit.name then -self.TowerFreq=unit.frequency/1000000 -return -end -end -end -function AIRBOSS:_GetGoodBadScore(playerData) -local lowscore -local badscore -if playerData.difficulty==AIRBOSS.Difficulty.EASY then -lowscore=10 -badscore=20 -elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then -lowscore=5 -badscore=10 -elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then -lowscore=2.5 -badscore=5 -end -return lowscore,badscore -end -function AIRBOSS:_IsCarrierAircraft(unit) -local aircrafttype=unit:GetTypeName() -if aircrafttype==AIRBOSS.AircraftCarrier.AV8B then -if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -return true -else -return false -end -end -if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -if aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then -return false -end -end -for _,actype in pairs(AIRBOSS.AircraftCarrier)do -if actype==aircrafttype then -return true -end -end -return false -end -function AIRBOSS:_IsHumanUnit(unit) -local playerunit=self:_GetPlayerUnitAndName(unit:GetName()) -if playerunit then -return true -else -return false -end -end -function AIRBOSS:_IsHuman(group) -local units=group:GetUnits() -for _,_unit in pairs(units)do -local human=self:_IsHumanUnit(_unit) -if human then -return true -end -end -return false -end -function AIRBOSS:_GetFuelState(unit) -local fuel=unit:GetFuel() -local maxfuel=self:_GetUnitMasses(unit) -local fuelstate=fuel*maxfuel -self:T2(self.lid..string.format("Unit %s fuel state = %.1f kg = %.1f lbs",unit:GetName(),fuelstate,UTILS.kg2lbs(fuelstate))) -return UTILS.kg2lbs(fuelstate) -end -function AIRBOSS:_GetAngels(alt) -if alt then -local angels=UTILS.Round(UTILS.MetersToFeet(alt)/1000,0) -return angels -else -return 0 -end -end -function AIRBOSS:_GetUnitMasses(unit) -local Desc=unit:GetDesc() -local massfuel=Desc.fuelMassMax or 0 -local massempty=Desc.massEmpty or 0 -local massmax=Desc.massMax or 0 -local masscargo=massmax-massfuel-massempty -self:T2(self.lid..string.format("Unit %s mass fuel=%.1f kg, empty=%.1f kg, max=%.1f kg, cargo=%.1f kg",unit:GetName(),massfuel,massempty,massmax,masscargo)) -return massfuel,massempty,massmax,masscargo -end -function AIRBOSS:_GetPlayerDataUnit(unit) -if unit:IsAlive()then -local unitname=unit:GetName() -local playerunit,playername=self:_GetPlayerUnitAndName(unitname) -if playerunit and playername then -return self.players[playername] -end -end -return nil -end -function AIRBOSS:_GetPlayerDataGroup(group) -local units=group:GetUnits() -for _,unit in pairs(units)do -local playerdata=self:_GetPlayerDataUnit(unit) -if playerdata then -return playerdata -end -end -return nil -end -function AIRBOSS:_GetPlayerUnit(_unitName) -for _,_player in pairs(self.players)do -local player=_player -if player.unit and player.unit:GetName()==_unitName then -self:T(self.lid..string.format("Found player=%s unit=%s in players table.",tostring(player.name),tostring(_unitName))) -return player.unit,player.name -end -end -return nil,nil -end -function AIRBOSS:_GetPlayerUnitAndName(_unitName) -self:F2(_unitName) -if _unitName~=nil then -local u,pn=self:_GetPlayerUnit(_unitName) -if u and pn then -return u,pn -end -local DCSunit=Unit.getByName(_unitName) -if DCSunit then -local playername=DCSunit:getPlayerName() -local unit=UNIT:Find(DCSunit) -self:T2({DCSunit=DCSunit,unit=unit,playername=playername}) -if DCSunit and unit and playername then -self:T(self.lid..string.format("Found DCS unit %s with player %s.",tostring(_unitName),tostring(playername))) -return unit,playername -end -end -end -return nil,nil -end -function AIRBOSS:GetCoalition() -return self.carrier:GetCoalition() -end -function AIRBOSS:GetCoordinate() -return self.carrier:GetCoord() -end -function AIRBOSS:GetCoord() -return self.carrier:GetCoord() -end -function AIRBOSS:_GetStaticWeather() -local weather=env.mission.weather -local clouds=weather.clouds -local visibility=weather.visibility.distance -local dust=nil -if weather.enable_dust==true then -dust=weather.dust_density -end -local fog=nil -if weather.enable_fog==true then -fog=weather.fog -end -return clouds,visibility,fog,dust -end -function AIRBOSS._CheckRadioQueueT(param,time) -AIRBOSS._CheckRadioQueue(param.airboss,param.radioqueue,param.name) -return time+0.05 -end -function AIRBOSS:_CheckRadioQueue(radioqueue,name) -if#radioqueue==0 then -if name=="LSO"then -self:T(self.lid..string.format("Stopping LSO radio queue.")) -self.radiotimer:Stop(self.RQLid) -self.RQLid=nil -elseif name=="MARSHAL"then -self:T(self.lid..string.format("Stopping Marshal radio queue.")) -self.radiotimer:Stop(self.RQMid) -self.RQMid=nil -end -return -end -local _time=timer.getAbsTime() -local playing=false -local next=nil -local _remove=nil -for i,_transmission in ipairs(radioqueue)do -local transmission=_transmission -if _time>=transmission.Tplay then -if transmission.isplaying then -if _time>=transmission.Tstarted+transmission.call.duration then -transmission.isplaying=false -_remove=i -if transmission.radio.alias=="LSO"then -self.TQLSO=_time -elseif transmission.radio.alias=="MARSHAL"then -self.TQMarshal=_time -end -else -playing=true -end -else -local Tlast=nil -if transmission.interval then -if transmission.radio.alias=="LSO"then -Tlast=self.TQLSO -elseif transmission.radio.alias=="MARSHAL"then -Tlast=self.TQMarshal -end -end -if transmission.interval==nil then -if next==nil then -next=transmission -end -else -if _time-Tlast>=transmission.interval then -next=transmission -else -end -end -if next or Tlast then -break -end -end -else -end -end -if next~=nil and not playing then -self:Broadcast(next.radio,next.call,next.loud) -next.isplaying=true -next.Tstarted=_time -end -if _remove then -table.remove(radioqueue,_remove) -end -return -end -function AIRBOSS:RadioTransmission(radio,call,loud,delay,interval,click,pilotcall) -self:F2({radio=radio,call=call,loud=loud,delay=delay,interval=interval,click=click}) -if radio==nil or call==nil then -return -end -if not self.SRS then -local transmission={} -transmission.radio=radio -transmission.call=call -transmission.Tplay=timer.getAbsTime()+(delay or 0) -transmission.interval=interval -transmission.isplaying=false -transmission.Tstarted=nil -transmission.loud=loud and call.loud -if self:_IsOnboard(call.modexsender)then -self:_Number2Radio(radio,call.modexsender,delay,0.3,pilotcall) -end -if self:_IsOnboard(call.modexreceiver)then -self:_Number2Radio(radio,call.modexreceiver,delay,0.3,pilotcall) -end -local caller="" -if radio.alias=="LSO"then -table.insert(self.RQLSO,transmission) -caller="LSOCall" -if not self.RQLid then -self:T(self.lid..string.format("Starting LSO radio queue.")) -self.RQLid=self.radiotimer:Schedule(nil,AIRBOSS._CheckRadioQueue,{self,self.RQLSO,"LSO"},0.02,0.05) -end -elseif radio.alias=="MARSHAL"then -table.insert(self.RQMarshal,transmission) -caller="MarshalCall" -if not self.RQMid then -self:T(self.lid..string.format("Starting Marhal radio queue.")) -self.RQMid=self.radiotimer:Schedule(nil,AIRBOSS._CheckRadioQueue,{self,self.RQMarshal,"MARSHAL"},0.02,0.05) -end -end -if click then -self:RadioTransmission(radio,self[caller].CLICK,false,delay) -end -else -if call.subtitle~=nil and string.len(call.subtitle)>1 then -local frequency=self.MarshalRadio.frequency -local modulation=self.MarshalRadio.modulation -local voice=nil -local gender=nil -local culture=nil -if radio.alias=="AIRBOSS"then -frequency=self.AirbossRadio.frequency -modulation=self.AirbossRadio.modulation -voice=self.AirbossRadio.voice -gender=self.AirbossRadio.gender -culture=self.AirbossRadio.culture -end -if radio.alias=="MARSHAL"then -voice=self.MarshalRadio.voice -gender=self.MarshalRadio.gender -culture=self.MarshalRadio.culture -end -if radio.alias=="LSO"then -frequency=self.LSORadio.frequency -modulation=self.LSORadio.modulation -voice=self.LSORadio.voice -gender=self.LSORadio.gender -culture=self.LSORadio.culture -end -if pilotcall then -voice=self.PilotRadio.voice -gender=self.PilotRadio.gender -culture=self.PilotRadio.culture -radio.alias="PILOT" -end -if not radio.alias then -frequency=self.AirbossRadio.frequency -modulation=self.AirbossRadio.modulation -radio.alias="AIRBOSS" -end -local volume=nil -if loud then -volume=1.0 -end -local text=call.subtitle -self:T(self.lid..text) -local srstext=self:_GetNiceSRSText(text) -self.SRSQ:NewTransmission(srstext,call.duration,self.SRS,nil,0.1,nil,call.subtitle,call.subduration,frequency,modulation,gender,culture,voice,volume,radio.alias) -end -end -end -function AIRBOSS:SetSRSPilotVoice(Voice,Gender,Culture) -self.PilotRadio={} -self.PilotRadio.alias="PILOT" -self.PilotRadio.voice=Voice or MSRS.Voices.Microsoft.David -self.PilotRadio.gender=Gender or"male" -self.PilotRadio.culture=Culture or"en-US" -if(not Voice)and self.SRS and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then -self.PilotRadio.voice=MSRS.Voices.Google.Standard.en_US_Standard_J -end -return self -end -function AIRBOSS:_NeedsSubtitle(call) -if call.file==self.MarshalCall.NOISE.file or call.file==self.LSOCall.NOISE.file then -return true -else -return false -end -end -function AIRBOSS:Broadcast(radio,call,loud) -self:F(call) -if not self.usersoundradio then -local sender=self:_GetRadioSender(radio) -local filename=self:_RadioFilename(call,loud,radio.alias) -local subtitle=self:_RadioSubtitle(radio,call,loud) -self:T({filename=filename,subtitle=subtitle}) -if sender then -self:T(self.lid..string.format("Broadcasting from aircraft %s",sender:GetName())) -local commandFrequency={ -id="SetFrequency", -params={ -frequency=radio.frequency*1000000, -modulation=radio.modulation, -}, -} -local commandTransmit={ -id="TransmitMessage", -params={ -file=filename, -duration=call.subduration or 5, -subtitle=subtitle, -loop=false, -}, -} -sender:SetCommand(commandFrequency) -sender:SetCommand(commandTransmit) -else -self:T(self.lid..string.format("Broadcasting from carrier via trigger.action.radioTransmission().")) -local vec3=self.carrier:GetPositionVec3() -trigger.action.radioTransmission(filename,vec3,radio.modulation,false,radio.frequency*1000000,100) -for _,_player in pairs(self.players)do -local playerData=_player -if playerData.unit:IsInZone(self.zoneCCA)and playerData.actype~=AIRBOSS.AircraftCarrier.A4EC then -if playerData.subtitles or self:_NeedsSubtitle(call)then -if radio.alias=="MARSHAL"or(radio.alias=="LSO"and self:_InQueue(self.Qpattern,playerData.group))then -self:MessageToPlayer(playerData,subtitle,nil,"",call.subduration or 5) -end -end -end -end -end -end -for _,_player in pairs(self.players)do -local playerData=_player -if self.usersoundradio or playerData.actype==AIRBOSS.AircraftCarrier.A4EC then -if radio.alias=="MARSHAL"or(radio.alias=="LSO"and self:_InQueue(self.Qpattern,playerData.group))then -self:Sound2Player(playerData,radio,call,loud) -end -end -end -end -function AIRBOSS:Sound2Player(playerData,radio,call,loud,delay) -if playerData.unit:IsInZone(self.zoneCCA)and call then -local filename=self:_RadioFilename(call,loud,radio.alias) -local subtitle=self:_RadioSubtitle(radio,call,loud) -USERSOUND:New(filename):ToGroup(playerData.group,delay) -if playerData.subtitles or self:_NeedsSubtitle(call)then -self:MessageToPlayer(playerData,subtitle,nil,"",call.subduration,false,delay) -end -end -end -function AIRBOSS:_RadioSubtitle(radio,call,loud) -if call==nil or call.subtitle==nil or call.subtitle==""then -return"" -end -local sender=call.sender or radio.alias -if call.modexsender then -sender=call.modexsender -end -local receiver=call.modexreceiver or"" -local subtitle=string.format("%s: %s",sender,call.subtitle) -if receiver and receiver~=""then -subtitle=string.format("%s: %s, %s",sender,receiver,call.subtitle) -end -local lastchar=string.sub(subtitle,-1) -if loud then -if lastchar=="."or lastchar=="!"then -subtitle=string.sub(subtitle,1,-1) -end -subtitle=subtitle.."!" -else -if lastchar=="!"then -elseif lastchar=="."then -else -subtitle=subtitle.."." -end -end -return subtitle -end -function AIRBOSS:_RadioFilename(call,loud,channel) -local prefix=call.file or"" -local suffix=call.suffix or"ogg" -local path=self.soundfolder or"l10n/DEFAULT/" -if string.find(call.file,"LSO-")and channel and(channel=="LSO"or channel=="LSOCall")then -path=self.soundfolderLSO or path -end -if string.find(call.file,"MARSHAL-")and channel and(channel=="MARSHAL"or channel=="MarshalCall")then -path=self.soundfolderMSH or path -end -if loud then -prefix=prefix.."_Loud" -end -local filename=string.format("%s%s.%s",path,prefix,suffix) -return filename -end -function AIRBOSS:_GetNiceSRSText(text) -text=string.gsub(text,"================================\n","") -text=string.gsub(text,"||","parallel") -text=string.gsub(text,"==","perpendicular") -text=string.gsub(text,"BRC","Base recovery") -text=string.gsub(text,"%((%a+)%)","Morse %1") -text=string.gsub(text,"°C","° Celsius") -text=string.gsub(text,"°"," degrees") -text=string.gsub(text," FB "," Final bearing ") -text=string.gsub(text," ops"," operations ") -text=string.gsub(text," kts"," knots") -text=string.gsub(text,"TACAN","Tackan") -text=string.gsub(text,"ICLS","I.C.L.S.") -text=string.gsub(text,"LSO","L.S.O.") -text=string.gsub(text,"inHg","inches of Mercury") -text=string.gsub(text,"QFE","Q.F.E.") -text=string.gsub(text,"hPa","hecto pascal") -text=string.gsub(text," NM"," nautical miles") -text=string.gsub(text," ft"," feet") -text=string.gsub(text,"A/C","aircraft") -text=string.gsub(text,"(#[%a%d%p%s]+)\n","") -text=string.gsub(text,"%.000"," dot zero") -text=string.gsub(text,"00"," double zero") -text=string.gsub(text," 0 "," zero ") -text=string.gsub(text,"\n","; ") -return text -end -function AIRBOSS:MessageToPlayer(playerData,message,sender,receiver,duration,clear,delay) -self:T({sender,receiver,message}) -if playerData and message and message~=""then -duration=duration or self.Tmessage -local text -if receiver and receiver==""then -text=string.format("%s",message) -else -receiver=receiver or playerData.onboard -text=string.format("%s, %s",receiver,message) -end -self:T(self.lid..text) -if delay and delay>0 then -self:ScheduleOnce(delay,self.MessageToPlayer,self,playerData,message,sender,receiver,duration,clear) -else -if not self.SRS then -local wait=0 -if receiver==playerData.onboard then -if sender and(sender=="LSO"or sender=="MARSHAL"or sender=="AIRBOSS")then -wait=wait+self:_Number2Sound(playerData,sender,receiver) -end -end -if string.find(text:lower(),"negative")then -local filename=self:_RadioFilename(self.MarshalCall.NEGATIVE,false,"MARSHAL") -USERSOUND:New(filename):ToGroup(playerData.group,wait) -wait=wait+self.MarshalCall.NEGATIVE.duration -end -if string.find(text:lower(),"affirm")then -local filename=self:_RadioFilename(self.MarshalCall.AFFIRMATIVE,false,"MARSHAL") -USERSOUND:New(filename):ToGroup(playerData.group,wait) -wait=wait+self.MarshalCall.AFFIRMATIVE.duration -end -if string.find(text:lower(),"roger")then -local filename=self:_RadioFilename(self.MarshalCall.ROGER,false,"MARSHAL") -USERSOUND:New(filename):ToGroup(playerData.group,wait) -wait=wait+self.MarshalCall.ROGER.duration -end -if wait>0 then -local filename=self:_RadioFilename(self.MarshalCall.CLICK) -USERSOUND:New(filename):ToGroup(playerData.group,wait) -end -else -local frequency=self.MarshalRadio.frequency -local modulation=self.MarshalRadio.modulation -local voice=self.MarshalRadio.voice -local gender=self.MarshalRadio.gender -local culture=self.MarshalRadio.culture -if not sender then sender="AIRBOSS"end -if string.find(sender,"AIRBOSS")then -frequency=self.AirbossRadio.frequency -modulation=self.AirbossRadio.modulation -voice=self.AirbossRadio.voice -gender=self.AirbossRadio.gender -culture=self.AirbossRadio.culture -end -if sender=="LSO"then -frequency=self.LSORadio.frequency -modulation=self.LSORadio.modulation -voice=self.LSORadio.voice -gender=self.LSORadio.gender -culture=self.LSORadio.culture -end -self:T(self.lid..text) -self:T({sender,frequency,modulation,voice}) -local srstext=self:_GetNiceSRSText(text) -self.SRSQ:NewTransmission(srstext,duration,self.SRS,nil,0.1,nil,nil,nil,frequency,modulation,gender,culture,voice,nil,sender) -end -if playerData.client then -MESSAGE:New(text,duration,sender,clear):ToClient(playerData.client) -end -end -end -end -function AIRBOSS:MessageToPattern(message,sender,receiver,duration,clear,delay) -local call=self:_NewRadioCall(self.LSOCall.NOISE,sender or"LSO",message,duration,receiver,sender) -self:RadioTransmission(self.LSORadio,call,false,delay,nil,true) -end -function AIRBOSS:MessageToMarshal(message,sender,receiver,duration,clear,delay) -local call=self:_NewRadioCall(self.MarshalCall.NOISE,sender or"MARSHAL",message,duration,receiver,sender) -self:RadioTransmission(self.MarshalRadio,call,false,delay,nil,true) -end -function AIRBOSS:_NewRadioCall(call,sender,subtitle,subduration,modexreceiver,modexsender) -local newcall=UTILS.DeepCopy(call) -newcall.sender=sender -newcall.subtitle=subtitle or call.subtitle -newcall.subduration=subduration or self.Tmessage -if self:_IsOnboard(modexreceiver)then -newcall.modexreceiver=modexreceiver -end -if self:_IsOnboard(modexsender)then -newcall.modexsender=modexsender -end -return newcall -end -function AIRBOSS:_GetRadioSender(radio) -local sender=nil -if self.senderac then -sender=UNIT:FindByName(self.senderac) -end -if radio.alias=="MARSHAL"then -if self.radiorelayMSH then -sender=UNIT:FindByName(self.radiorelayMSH) -end -end -if radio.alias=="LSO"then -if self.radiorelayLSO then -sender=UNIT:FindByName(self.radiorelayLSO) -end -end -if sender and sender:IsAlive()and sender:IsAir()then -return sender -end -return nil -end -function AIRBOSS:_IsOnboard(text) -if text==nil then -return false -end -if text=="99"then -return true -end -for _,_flight in pairs(self.flights)do -local flight=_flight -for _,onboard in pairs(flight.onboardnumbers)do -if text==onboard then -return true -end -end -end -return false -end -function AIRBOSS:_Number2Sound(playerData,sender,number,delay) -delay=delay or 0 -local function _split(str) -local chars={} -for i=1,#str do -local c=str:sub(i,i) -table.insert(chars,c) -end -return chars -end -local Sender -if sender=="LSO"then -Sender="LSOCall" -elseif sender=="MARSHAL"or sender=="AIRBOSS"then -Sender="MarshalCall" -else -self:E(self.lid..string.format("ERROR: Unknown radio sender %s!",tostring(sender))) -return -end -local numbers=_split(number) -local wait=0 -for i=1,#numbers do -local n=numbers[i] -local N=string.format("N%s",n) -local call=self[Sender][N] -local filename=self:_RadioFilename(call,false,Sender) -USERSOUND:New(filename):ToGroup(playerData.group,delay+wait) -wait=wait+call.duration -end -return wait -end -function AIRBOSS:_Number2Radio(radio,number,delay,interval,pilotcall) -local function _split(str) -local chars={} -for i=1,#str do -local c=str:sub(i,i) -table.insert(chars,c) -end -return chars -end -local Sender="" -if radio.alias=="LSO"then -Sender="LSOCall" -elseif radio.alias=="MARSHAL"then -Sender="MarshalCall" -else -self:E(self.lid..string.format("ERROR: Unknown radio alias %s!",tostring(radio.alias))) -end -if pilotcall then -Sender="PilotCall" -end -if Sender==""then -self:E(self.lid..string.format("ERROR: Sender unknown!")) -return -end -local numbers=_split(number) -local wait=0 -for i=1,#numbers do -local n=numbers[i] -local N=string.format("N%s",n) -local call=self[Sender][N] -if interval and i==1 then -self:RadioTransmission(radio,call,false,delay,interval) -else -self:RadioTransmission(radio,call,false,delay) -end -wait=wait+call.duration -end -return wait -end -function AIRBOSS:_MarshallInboundCall(unit,modex) -local vectorCarrier=self:GetCoordinate():GetDirectionVec3(unit:GetCoordinate()) -local bearing=UTILS.Round(unit:GetCoordinate():GetAngleDegrees(vectorCarrier),0) -local distance=UTILS.Round(UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())),0) -local angels=UTILS.Round(UTILS.MetersToFeet(unit:GetHeight()/1000),0) -local state=UTILS.Round(self:_GetFuelState(unit)/1000,1) -local text=string.format("Marshal, %s, marking mom's %d for %d, angels %d, state %.1f",modex,bearing,distance,angels,state) -self:T(self.lid..text) -local FS=UTILS.Split(string.format("%.1f",state),".") -local inboundcall=self:_NewRadioCall(self.MarshalCall.CLICK,unit.UnitName:upper(),text,self.Tmessage,nil,unit.UnitName:upper()) -self:RadioTransmission(self.MarshalRadio,inboundcall) -self:RadioTransmission(self.MarshalRadio,self.PilotCall.MARSHAL,nil,nil,nil,nil,true) -self:_Number2Radio(self.MarshalRadio,modex,nil,nil,true) -self:RadioTransmission(self.MarshalRadio,self.PilotCall.MARKINGMOMS,nil,nil,nil,nil,true) -self:_Number2Radio(self.MarshalRadio,tostring(bearing),nil,nil,true) -self:RadioTransmission(self.MarshalRadio,self.PilotCall.FOR,nil,nil,nil,nil,true) -self:_Number2Radio(self.MarshalRadio,tostring(distance),nil,nil,true) -self:RadioTransmission(self.MarshalRadio,self.PilotCall.ANGELS,nil,nil,nil,nil,true) -self:_Number2Radio(self.MarshalRadio,tostring(angels),nil,nil,true) -self:RadioTransmission(self.MarshalRadio,self.PilotCall.STATE,nil,nil,nil,nil,true) -self:_Number2Radio(self.MarshalRadio,FS[1],nil,nil,true) -self:RadioTransmission(self.MarshalRadio,self.PilotCall.POINT,nil,nil,nil,nil,true) -self:_Number2Radio(self.MarshalRadio,FS[2],nil,nil,true) -self:RadioTransmission(self.MarshalRadio,self.MarshalRadio.CLICK,nil,nil,nil,nil,true) -end -function AIRBOSS:_CommencingCall(unit,modex) -local text=string.format("%s, commencing",modex) -self:T(self.lid..text) -local commencingCall=self:_NewRadioCall(self.MarshalCall.CLICK,unit.UnitName:upper(),text,self.Tmessage,nil,unit.UnitName:upper()) -self:RadioTransmission(self.MarshalRadio,commencingCall) -self:_Number2Radio(self.MarshalRadio,modex,nil,nil,true) -self:RadioTransmission(self.MarshalRadio,self.PilotCall.COMMENCING,nil,nil,nil,nil,true) -self:RadioTransmission(self.MarshalRadio,self.MarshalRadio.CLICK,nil,nil,nil,nil,true) -end -function AIRBOSS:_LSOCallAircraftBall(modex,nickname,fuelstate) -local text=string.format("%s Ball, %.1f.",nickname,fuelstate) -self:T(self.lid..text) -local NICKNAME=nickname:upper() -local FS=UTILS.Split(string.format("%.1f",fuelstate),".") -local call=self:_NewRadioCall(self.PilotCall[NICKNAME],modex,text,self.Tmessage,nil,modex) -self:RadioTransmission(self.LSORadio,call,nil,nil,nil,nil,true) -self:RadioTransmission(self.LSORadio,self.PilotCall.BALL,nil,nil,nil,nil,true) -self:_Number2Radio(self.LSORadio,FS[1],nil,nil,true) -self:RadioTransmission(self.LSORadio,self.PilotCall.POINT,nil,nil,nil,nil,true) -self:_Number2Radio(self.LSORadio,FS[2],nil,nil,true) -self:RadioTransmission(self.LSORadio,self.LSOCall.CLICK) -end -function AIRBOSS:_MarshalCallGasAtTanker(modex) -local text=string.format("Bingo fuel! Going for gas at the recovery tanker.") -self:T(self.lid..text) -local call=self:_NewRadioCall(self.PilotCall.BINGOFUEL,modex,text,self.Tmessage,nil,modex) -self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,nil,true) -self:RadioTransmission(self.MarshalRadio,self.PilotCall.GASATTANKER,nil,nil,nil,true,true) -end -function AIRBOSS:_MarshalCallGasAtDivert(modex,divertname) -local text=string.format("Bingo fuel! Going for gas at divert field %s.",divertname) -self:T(self.lid..text) -local call=self:_NewRadioCall(self.PilotCall.BINGOFUEL,modex,text,self.Tmessage,nil,modex) -self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,nil,true) -self:RadioTransmission(self.MarshalRadio,self.PilotCall.GASATDIVERT,nil,nil,nil,true,true) -end -function AIRBOSS:_MarshalCallRecoveryStopped(case) -local text=string.format("Case %d recovery ops are stopped. Deck is closed.",case) -self:T(self.lid..text) -local call=self:_NewRadioCall(self.MarshalCall.CASE,"AIRBOSS",text,self.Tmessage,"99") -self:RadioTransmission(self.MarshalRadio,call) -self:_Number2Radio(self.MarshalRadio,tostring(case)) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RECOVERYOPSSTOPPED,nil,nil,0.2) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DECKCLOSED,nil,nil,nil,true) -end -function AIRBOSS:_MarshalCallRecoveryPausedUntilFurtherNotice() -local call=self:_NewRadioCall(self.MarshalCall.RECOVERYPAUSEDNOTICE,"AIRBOSS",nil,self.Tmessage,"99") -self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,true) -end -function AIRBOSS:_MarshalCallRecoveryPausedResumedAt(clock) -local _clock=UTILS.Split(clock,"+") -local CT=UTILS.Split(_clock[1],":") -local text=string.format("aircraft recovery is paused and will be resumed at %s.",clock) -self:T(self.lid..text) -local call=self:_NewRadioCall(self.MarshalCall.RECOVERYPAUSEDRESUMED,"AIRBOSS",text,self.Tmessage,"99") -self:RadioTransmission(self.MarshalRadio,call) -self:_Number2Radio(self.MarshalRadio,CT[1]) -self:_Number2Radio(self.MarshalRadio,CT[2]) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.HOURS,nil,nil,nil,true) -end -function AIRBOSS:_MarshalCallClearedForRecovery(modex,case) -local text=string.format("you're cleared for Case %d recovery.",case) -self:T(self.lid..text) -local call=self:_NewRadioCall(self.MarshalCall.CLEAREDFORRECOVERY,"MARSHAL",text,self.Tmessage,modex) -local delay=2 -self:RadioTransmission(self.MarshalRadio,call,nil,delay) -self:_Number2Radio(self.MarshalRadio,tostring(case),delay) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RECOVERY,nil,delay,nil,true) -end -function AIRBOSS:_MarshalCallResumeRecovery() -local call=self:_NewRadioCall(self.MarshalCall.RESUMERECOVERY,"AIRBOSS",nil,self.Tmessage,"99") -self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,true) -end -function AIRBOSS:_MarshalCallNewFinalBearing(FB) -local text=string.format("new final bearing %03d°.",FB) -self:T(self.lid..text) -local call=self:_NewRadioCall(self.MarshalCall.NEWFB,"AIRBOSS",text,self.Tmessage,"99") -self:RadioTransmission(self.MarshalRadio,call) -self:_Number2Radio(self.MarshalRadio,string.format("%03d",FB),nil,0.2) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES,nil,nil,nil,true) -end -function AIRBOSS:_MarshalCallCarrierTurnTo(hdg) -local text=string.format("carrier is now starting turn to heading %03d°.",hdg) -self:T(self.lid..text) -local call=self:_NewRadioCall(self.MarshalCall.CARRIERTURNTOHEADING,"AIRBOSS",text,self.Tmessage,"99") -self:RadioTransmission(self.MarshalRadio,call) -self:_Number2Radio(self.MarshalRadio,string.format("%03d",hdg),nil,0.2) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES,nil,nil,nil,true) -end -function AIRBOSS:_MarshalCallStackFull(modex,nwaiting) -local text=string.format("Marshal stack is currently full. Hold outside 10 NM zone and wait for further instructions. ") -if nwaiting==1 then -text=text..string.format("There is one flight ahead of you.") -elseif nwaiting>1 then -text=text..string.format("There are %d flights ahead of you.",nwaiting) -else -text=text..string.format("You are next in line.") -end -self:T(self.lid..text) -local call=self:_NewRadioCall(self.MarshalCall.STACKFULL,"AIRBOSS",text,self.Tmessage,modex) -self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,true) -end -function AIRBOSS:_MarshalCallRecoveryStart(case) -local radial=self:GetRadial(case,true,true,false) -local text=string.format("Starting aircraft recovery Case %d ops.",case) -if case==1 then -text=text..string.format(" BRC %03d°.",self:GetBRC()) -elseif case==2 then -text=text..string.format(" Marshal radial %03d°. BRC %03d°.",radial,self:GetBRC()) -elseif case==3 then -text=text..string.format(" Marshal radial %03d°. Final heading %03d°.",radial,self:GetFinalBearing(false)) -end -self:T(self.lid..text) -local call=self:_NewRadioCall(self.MarshalCall.STARTINGRECOVERY,"AIRBOSS",text,self.Tmessage,"99") -self:RadioTransmission(self.MarshalRadio,call) -self:_Number2Radio(self.MarshalRadio,tostring(case),nil,0.1) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.OPS) -if case>1 then -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.MARSHALRADIAL) -self:_Number2Radio(self.MarshalRadio,string.format("%03d",radial),nil,0.2) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES,nil,nil,nil,true) -end -end -function AIRBOSS:_MarshalCallArrived(modex,case,brc,altitude,charlie,qfe) -self:F({modex=modex,case=case,brc=brc,altitude=altitude,charlie=charlie,qfe=qfe}) -local angels=self:_GetAngels(altitude) -local QFE=UTILS.Split(string.format("%.2f",qfe),".") -local clock=UTILS.Split(charlie,"+") -local CT=UTILS.Split(clock[1],":") -local text=string.format("Case %d, expected BRC %03d°, hold at angels %d. Expected Charlie Time %s. Altimeter %.2f. Report see me.",case,brc,angels,charlie,qfe) -self:T(self.lid..text) -local casecall=self:_NewRadioCall(self.MarshalCall.CASE,"MARSHAL",text,self.Tmessage,modex) -self:RadioTransmission(self.MarshalRadio,casecall) -self:_Number2Radio(self.MarshalRadio,tostring(case)) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.EXPECTED,nil,nil,0.5) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.BRC) -self:_Number2Radio(self.MarshalRadio,string.format("%03d",brc)) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.HOLDATANGELS,nil,nil,0.5) -self:_Number2Radio(self.MarshalRadio,tostring(angels)) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.EXPECTED,nil,nil,0.5) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.CHARLIETIME) -self:_Number2Radio(self.MarshalRadio,CT[1]) -self:_Number2Radio(self.MarshalRadio,CT[2]) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.HOURS) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.ALTIMETER,nil,nil,0.5) -self:_Number2Radio(self.MarshalRadio,QFE[1]) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.POINT) -self:_Number2Radio(self.MarshalRadio,QFE[2]) -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.REPORTSEEME,nil,nil,0.5,true) -end -function AIRBOSS:_AddF10Commands(_unitName) -self:F(_unitName) -local _unit,playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and playername then -local group=_unit:GetGroup() -local gid=group:GetID() -if group and gid then -if not self.menuadded[gid]then -self.menuadded[gid]=true -local _rootPath=nil -if AIRBOSS.MenuF10Root then -if self.menusingle then -_rootPath=AIRBOSS.MenuF10Root -else -_rootPath=missionCommands.addSubMenuForGroup(gid,self.alias,AIRBOSS.MenuF10Root) -end -else -if AIRBOSS.MenuF10[gid]==nil then -AIRBOSS.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid,"Airboss") -end -if self.menusingle then -_rootPath=AIRBOSS.MenuF10[gid] -else -_rootPath=missionCommands.addSubMenuForGroup(gid,self.alias,AIRBOSS.MenuF10[gid]) -end -end -local _helpPath=missionCommands.addSubMenuForGroup(gid,"Help",_rootPath) -if self.menumarkzones then -local _markPath=missionCommands.addSubMenuForGroup(gid,"Mark Zones",_helpPath) -if self.menusmokezones then -missionCommands.addCommandForGroup(gid,"Smoke Pattern Zones",_markPath,self._MarkCaseZones,self,_unitName,false) -end -missionCommands.addCommandForGroup(gid,"Flare Pattern Zones",_markPath,self._MarkCaseZones,self,_unitName,true) -if self.menusmokezones then -missionCommands.addCommandForGroup(gid,"Smoke Marshal Zone",_markPath,self._MarkMarshalZone,self,_unitName,false) -end -missionCommands.addCommandForGroup(gid,"Flare Marshal Zone",_markPath,self._MarkMarshalZone,self,_unitName,true) -end -local _skillPath=missionCommands.addSubMenuForGroup(gid,"Skill Level",_helpPath) -missionCommands.addCommandForGroup(gid,"Flight Student",_skillPath,self._SetDifficulty,self,_unitName,AIRBOSS.Difficulty.EASY) -missionCommands.addCommandForGroup(gid,"Naval Aviator",_skillPath,self._SetDifficulty,self,_unitName,AIRBOSS.Difficulty.NORMAL) -missionCommands.addCommandForGroup(gid,"TOPGUN Graduate",_skillPath,self._SetDifficulty,self,_unitName,AIRBOSS.Difficulty.HARD) -missionCommands.addCommandForGroup(gid,"Hints On/Off",_skillPath,self._SetHintsOnOff,self,_unitName) -missionCommands.addCommandForGroup(gid,"My Status",_helpPath,self._DisplayPlayerStatus,self,_unitName) -missionCommands.addCommandForGroup(gid,"Attitude Monitor",_helpPath,self._DisplayAttitude,self,_unitName) -missionCommands.addCommandForGroup(gid,"Radio Check LSO",_helpPath,self._LSORadioCheck,self,_unitName) -missionCommands.addCommandForGroup(gid,"Radio Check Marshal",_helpPath,self._MarshalRadioCheck,self,_unitName) -missionCommands.addCommandForGroup(gid,"Subtitles On/Off",_helpPath,self._SubtitlesOnOff,self,_unitName) -missionCommands.addCommandForGroup(gid,"Trapsheet On/Off",_helpPath,self._TrapsheetOnOff,self,_unitName) -local _kneeboardPath=missionCommands.addSubMenuForGroup(gid,"Kneeboard",_rootPath) -local _resultsPath=missionCommands.addSubMenuForGroup(gid,"Results",_kneeboardPath) -missionCommands.addCommandForGroup(gid,"Greenie Board",_resultsPath,self._DisplayScoreBoard,self,_unitName) -missionCommands.addCommandForGroup(gid,"My LSO Grades",_resultsPath,self._DisplayPlayerGrades,self,_unitName) -missionCommands.addCommandForGroup(gid,"Last Debrief",_resultsPath,self._DisplayDebriefing,self,_unitName) -if self.skipperMenu then -local _skipperPath=missionCommands.addSubMenuForGroup(gid,"Skipper",_kneeboardPath) -local _menusetspeed=missionCommands.addSubMenuForGroup(gid,"Set Speed",_skipperPath) -missionCommands.addCommandForGroup(gid,"10 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,10) -missionCommands.addCommandForGroup(gid,"15 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,15) -missionCommands.addCommandForGroup(gid,"20 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,20) -missionCommands.addCommandForGroup(gid,"25 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,25) -missionCommands.addCommandForGroup(gid,"30 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,30) -local _menusetrtime=missionCommands.addSubMenuForGroup(gid,"Set Time",_skipperPath) -missionCommands.addCommandForGroup(gid,"15 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,15) -missionCommands.addCommandForGroup(gid,"30 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,30) -missionCommands.addCommandForGroup(gid,"45 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,45) -missionCommands.addCommandForGroup(gid,"60 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,60) -missionCommands.addCommandForGroup(gid,"90 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,90) -local _menusetrtime=missionCommands.addSubMenuForGroup(gid,"Set Marshal Radial",_skipperPath) -missionCommands.addCommandForGroup(gid,"+30°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,30) -missionCommands.addCommandForGroup(gid,"+15°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,15) -missionCommands.addCommandForGroup(gid,"0°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,0) -missionCommands.addCommandForGroup(gid,"-15°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,-15) -missionCommands.addCommandForGroup(gid,"-30°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,-30) -missionCommands.addCommandForGroup(gid,"U-turn On/Off",_skipperPath,self._SkipperRecoveryUturn,self,_unitName) -missionCommands.addCommandForGroup(gid,"Start CASE I",_skipperPath,self._SkipperStartRecovery,self,_unitName,1) -missionCommands.addCommandForGroup(gid,"Start CASE II",_skipperPath,self._SkipperStartRecovery,self,_unitName,2) -missionCommands.addCommandForGroup(gid,"Start CASE III",_skipperPath,self._SkipperStartRecovery,self,_unitName,3) -missionCommands.addCommandForGroup(gid,"Stop Recovery",_skipperPath,self._SkipperStopRecovery,self,_unitName) -end -missionCommands.addCommandForGroup(gid,"Carrier Info",_kneeboardPath,self._DisplayCarrierInfo,self,_unitName) -missionCommands.addCommandForGroup(gid,"Weather Report",_kneeboardPath,self._DisplayCarrierWeather,self,_unitName) -missionCommands.addCommandForGroup(gid,"Set Section",_kneeboardPath,self._SetSection,self,_unitName) -missionCommands.addCommandForGroup(gid,"Marshal Queue",_kneeboardPath,self._DisplayQueue,self,_unitName,"Marshal") -missionCommands.addCommandForGroup(gid,"Pattern Queue",_kneeboardPath,self._DisplayQueue,self,_unitName,"Pattern") -missionCommands.addCommandForGroup(gid,"Waiting Queue",_kneeboardPath,self._DisplayQueue,self,_unitName,"Waiting") -missionCommands.addCommandForGroup(gid,"Request Marshal",_rootPath,self._RequestMarshal,self,_unitName) -missionCommands.addCommandForGroup(gid,"Request Commence",_rootPath,self._RequestCommence,self,_unitName) -missionCommands.addCommandForGroup(gid,"Request Refueling",_rootPath,self._RequestRefueling,self,_unitName) -missionCommands.addCommandForGroup(gid,"Spinning",_rootPath,self._RequestSpinning,self,_unitName) -missionCommands.addCommandForGroup(gid,"Emergency Landing",_rootPath,self._RequestEmergency,self,_unitName) -missionCommands.addCommandForGroup(gid,"[Reset My Status]",_rootPath,self._ResetPlayerStatus,self,_unitName) -end -else -self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.",_unitName)) -end -else -self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.",_unitName)) -end -end -function AIRBOSS:_SkipperStartRecovery(_unitName,case) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local text=string.format("affirm, Case %d recovery will start in 5 min for %d min. Wind on deck %d knots. U-turn=%s.",case,self.skipperTime,self.skipperSpeed,tostring(self.skipperUturn)) -if case>1 then -text=text..string.format(" Marshal radial %d°.",self.skipperOffset) -end -if self:IsRecovering()then -text="negative, carrier is already recovering." -self:MessageToPlayer(playerData,text,"AIRBOSS") -return -end -self:MessageToPlayer(playerData,text,"AIRBOSS") -local t0=timer.getAbsTime()+5*60 -local t9=t0+self.skipperTime*60 -local C0=UTILS.SecondsToClock(t0) -local C9=UTILS.SecondsToClock(t9) -self:AddRecoveryWindow(C0,C9,case,self.skipperOffset,true,self.skipperSpeed,self.skipperUturn) -end -end -end -function AIRBOSS:_SkipperStopRecovery(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local text="roger, stopping recovery right away." -if not self:IsRecovering()then -text="negative, carrier is currently not recovering." -self:MessageToPlayer(playerData,text,"AIRBOSS") -return -end -self:MessageToPlayer(playerData,text,"AIRBOSS") -self:RecoveryStop() -end -end -end -function AIRBOSS:_SkipperRecoveryOffset(_unitName,offset) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.",offset) -self:MessageToPlayer(playerData,text,"AIRBOSS") -self.skipperOffset=offset -end -end -end -function AIRBOSS:_SkipperRecoveryTime(_unitName,time) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local text=string.format("roger, manual recovery time set to %d min.",time) -self:MessageToPlayer(playerData,text,"AIRBOSS") -self.skipperTime=time -end -end -end -function AIRBOSS:_SkipperRecoverySpeed(_unitName,speed) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local text=string.format("roger, wind on deck set to %d knots.",speed) -self:MessageToPlayer(playerData,text,"AIRBOSS") -self.skipperSpeed=speed -end -end -end -function AIRBOSS:_SkipperRecoveryUturn(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -self.skipperUturn=not self.skipperUturn -local text=string.format("roger, U-turn is now %s.",tostring(self.skipperUturn)) -self:MessageToPlayer(playerData,text,"AIRBOSS") -end -end -end -function AIRBOSS:_ResetPlayerStatus(_unitName) -self:F(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local text="roger, status reset executed! You have been removed from all queues." -self:MessageToPlayer(playerData,text,"AIRBOSS") -self:_RemoveFlight(playerData) -if playerData.debriefschedulerID and self.Scheduler then -self.Scheduler:Stop(playerData.debriefschedulerID) -end -self:_InitPlayer(playerData) -end -end -end -function AIRBOSS:_RequestMarshal(_unitName) -self:F(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -if self.xtVoiceOvers then -self:_MarshallInboundCall(_unit,playerData.onboard) -end -local inCCA=playerData.unit:IsInZone(self.zoneCCA) -if inCCA then -if self:_InQueue(self.Qmarshal,playerData.group)then -local text=string.format("negative, you are already in the Marshal queue. New marshal request denied!") -self:MessageToPlayer(playerData,text,"MARSHAL") -elseif self:_InQueue(self.Qpattern,playerData.group)then -local text=string.format("negative, you are already in the Pattern queue. Marshal request denied!") -self:MessageToPlayer(playerData,text,"MARSHAL") -elseif self:_InQueue(self.Qwaiting,playerData.group)then -local text=string.format("negative, you are in the Waiting queue with %d flights ahead of you. Marshal request denied!",#self.Qwaiting) -self:MessageToPlayer(playerData,text,"MARSHAL") -elseif not _unit:InAir()then -local text=string.format("negative, you are not airborne. Marshal request denied!") -self:MessageToPlayer(playerData,text,"MARSHAL") -elseif playerData.name~=playerData.seclead then -local text=string.format("negative, your section lead %s needs to request Marshal.",playerData.seclead) -self:MessageToPlayer(playerData,text,"MARSHAL") -else -local freestack=self:_GetFreeStack(playerData.ai) -if freestack then -self:_MarshalPlayer(playerData,freestack) -else -self:_WaitPlayer(playerData) -end -end -else -local text=string.format("negative, you are not inside CCA. Marshal request denied!") -self:MessageToPlayer(playerData,text,"MARSHAL") -end -end -end -end -function AIRBOSS:_RequestEmergency(_unitName) -self:F(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local text="" -if not self.emergency then -text="negative, no emergency landings on my carrier. We are currently busy. See how you get along!" -elseif not _unit:InAir()then -local zone=self:_GetZoneCarrierBox() -if playerData.unit:IsInZone(zone)then -text="roger, you are now technically in the bolter pattern. Your next step after takeoff is abeam!" -local lead=self:_GetFlightLead(playerData) -self:_SetPlayerStep(lead,AIRBOSS.PatternStep.BOLTER) -for _,sec in pairs(lead.section)do -local sectionmember=sec -self:_SetPlayerStep(sectionmember,AIRBOSS.PatternStep.BOLTER) -end -self:_RemoveFlightFromQueue(self.Qwaiting,lead) -if self:_InQueue(self.Qmarshal,lead.group)then -self:_RemoveFlightFromMarshalQueue(lead) -else -if not self:_InQueue(self.Qpattern,lead.group)then -self:_AddFlightToPatternQueue(lead) -end -end -else -text=string.format("negative, you are not airborne. Request denied!") -end -else -text="affirmative, you can bypass the pattern and are cleared for final approach!" -local lead=self:_GetFlightLead(playerData) -self:_SetPlayerStep(lead,AIRBOSS.PatternStep.EMERGENCY) -for _,sec in pairs(lead.section)do -local sectionmember=sec -self:_SetPlayerStep(sectionmember,AIRBOSS.PatternStep.EMERGENCY) -self:_RemoveFlightFromQueue(self.Qspinning,sectionmember) -end -self:_RemoveFlightFromQueue(self.Qwaiting,lead) -if self:_InQueue(self.Qmarshal,lead.group)then -self:_RemoveFlightFromMarshalQueue(lead) -else -if not self:_InQueue(self.Qpattern,lead.group)then -self:_AddFlightToPatternQueue(lead) -end -end -end -self:MessageToPlayer(playerData,text,"AIRBOSS") -end -end -end -function AIRBOSS:_RequestSpinning(_unitName) -self:F(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local text="" -if not self:_InQueue(self.Qpattern,playerData.group)then -text="negative, you have to be in the pattern to spin it!" -elseif playerData.step==AIRBOSS.PatternStep.SPINNING then -text="negative, you are already spinning." -elseif not(playerData.step==AIRBOSS.PatternStep.BREAKENTRY or -playerData.step==AIRBOSS.PatternStep.EARLYBREAK or -playerData.step==AIRBOSS.PatternStep.LATEBREAK)then -text="negative, you have to be in the right step to spin it!" -else -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.SPINNING) -table.insert(self.Qspinning,playerData) -local call=self:_NewRadioCall(self.LSOCall.SPINIT,"AIRBOSS","Spin it!",self.Tmessage,playerData.onboard) -self:RadioTransmission(self.LSORadio,call,nil,nil,nil,true) -if playerData.difficulty==AIRBOSS.Difficulty.EASY then -local text="Climb to 1200 feet and proceed to the initial again." -self:MessageToPlayer(playerData,text,"AIRBOSS","") -end -return -end -self:MessageToPlayer(playerData,text,"AIRBOSS") -end -end -end -function AIRBOSS:_RequestCommence(_unitName) -self:F(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -if self.xtVoiceOvers then -self:_CommencingCall(_unit,playerData.onboard) -end -local text="" -local cleared=false -if _unit:IsInZone(self.zoneCCA)then -local stack=playerData.flag -local _,npattern=self:_GetQueueInfo(self.Qpattern) -if self:_InQueue(self.Qpattern,playerData.group)then -text=string.format("negative, %s, you are already in the Pattern queue.",playerData.name) -elseif not _unit:InAir()then -text=string.format("negative, %s, you are not airborne.",playerData.name) -elseif playerData.seclead~=playerData.name then -text=string.format("negative, %s, your section leader %s has to request commence!",playerData.name,playerData.seclead) -elseif stack>1 then -text=string.format("negative, %s, it's not your turn yet! You are in stack no. %s.",playerData.name,stack) -elseif npattern>=self.Nmaxpattern then -text=string.format("negative ghostrider, pattern is full!\nThere are %d aircraft currently in the pattern.",npattern) -elseif self:IsRecovering()==false and not self.airbossnice then -if self.recoverywindow then -local clock=UTILS.SecondsToClock(self.recoverywindow.START) -text=string.format("negative, carrier is currently not recovery. Next window will open at %s.",clock) -else -text=string.format("negative, carrier is not recovering. No future windows planned.") -end -elseif not self:_InQueue(self.Qmarshal,playerData.group)and not self.airbossnice then -text="negative, you have to request Marshal before you can commence." -else -text=text.."roger." -if not self:IsRecovering()then -text=text.." Carrier is not recovering currently! However, you are cleared anyway as I have a nice day." -end -if not self:_InQueue(self.Qmarshal,playerData.group)then -playerData.case=self.case -if self.TACANon and playerData.difficulty~=AIRBOSS.Difficulty.HARD then -local radial=self:GetRadial(playerData.case,true,true,true) -if playerData.case==1 then -radial=self:GetBRC() -end -text=text..string.format("\nSelect TACAN %03d°, Channel %d%s (%s).\n",radial,self.TACANchannel,self.TACANmode,self.TACANmorse) -end -for _,flight in pairs(playerData.section)do -flight.case=playerData.case -end -self:_AddFlightToPatternQueue(playerData) -end -cleared=true -end -else -text=string.format("negative, %s, you are not inside the CCA!",playerData.name) -end -self:T(self.lid..text) -self:MessageToPlayer(playerData,text,"MARSHAL") -if cleared then -self:_Commencing(playerData,false) -end -end -end -end -function AIRBOSS:_RequestRefueling(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local text -if self.tanker then -if _unit:IsInZone(self.zoneCCA)then -if self.tanker:IsRunning()or self.tanker:IsRefueling()then -local angels=self:_GetAngels(self.tanker.altitude) -text=string.format("affirmative, proceed to tanker at angels %d.",angels) -if self.tanker.TACANon then -text=text..string.format("\nTanker TACAN channel %d%s (%s).",self.tanker.TACANchannel,self.tanker.TACANmode,self.tanker.TACANmorse) -text=text..string.format("\nRadio frequency %.3f MHz AM.",self.tanker.RadioFreq) -end -if self.tanker:IsRefueling()then -text=text.."\nTanker is currently refueling. You might have to queue up." -end -self:_RemoveFlightFromMarshalQueue(playerData,true) -self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.REFUELING) -for _,sec in pairs(playerData.section)do -local sectext="follow your section leader to the tanker." -self:MessageToPlayer(sec,sectext,"MARSHAL") -self:_SetPlayerStep(sec,AIRBOSS.PatternStep.REFUELING) -end -elseif self.tanker:IsReturning()then -text="negative, tanker is RTB. Request denied!\nWait for the tanker to be back on station if you can." -end -else -text="negative, you are not inside the CCA yet." -end -else -text="negative, no refueling tanker available." -end -self:MessageToPlayer(playerData,text,"MARSHAL") -end -end -end -function AIRBOSS:_RemoveSectionMember(playerData,sectionmember) -for i,_flight in pairs(playerData.section)do -local flight=_flight -if flight.name==sectionmember.name then -table.remove(playerData.section,i) -return true -end -end -return false -end -function AIRBOSS:_SetSection(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local mycoord=_unit:GetCoordinate() -local dmax=100 -local text -if self.NmaxSection==0 then -text=string.format("negative, setting sections is disabled in this mission. You stay alone.") -elseif self:_InQueue(self.Qmarshal,playerData.group)then -text=string.format("negative, you are already in the Marshal queue. Setting section not possible any more!") -elseif self:_InQueue(self.Qpattern,playerData.group)then -text=string.format("negative, you are already in the Pattern queue. Setting section not possible any more!") -else -if playerData.seclead~=playerData.name then -local lead=self.players[playerData.seclead] -if lead then -local removed=self:_RemoveSectionMember(lead,playerData) -if removed then -self:MessageToPlayer(lead,string.format("Flight %s has been removed from your section.",playerData.name),"AIRBOSS","",5) -self:MessageToPlayer(playerData,string.format("You have been removed from %s's section.",lead.name),"AIRBOSS","",5) -end -end -end -local section={} -for _,_flight in pairs(self.flights)do -local flight=_flight -if flight.ai==false and flight.groupname~=playerData.groupname and#flight.section==0 and flight.seclead==flight.name then -local distance=flight.group:GetCoordinate():Get3DDistance(mycoord) -if distance0 then -_playerResults[playerName]=Paverage/n -end -end -end -local text=string.format("Greenie Board (top ten):") -local i=1 -for _playerName,_points in UTILS.spairs(_playerResults,function(t,a,b) -return t[b]=0 then -text=text..string.format("(%.1f)",grade.points) -end -end -i=i+1 -if i>10 then -break -end -end -if i==1 then -text=text.."\nNo results yet." -end -local playerData=self.players[_playername] -if playerData.client then -MESSAGE:New(text,30,nil,true):ToClient(playerData.client) -end -end -end -function AIRBOSS:_DisplayPlayerGrades(_unitName) -self:F(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local text=string.format("Your last 10 grades, %s:",_playername) -local playerGrades=self.playerscores[_playername]or{} -local p=0 -local n=0 -local m=0 -for i=#playerGrades,1,-1 do -local grade=playerGrades[i] -if grade.points>=0 then -local points=grade.finalscore or grade.points -if m<10 then -text=text..string.format("\n[%d] %s %.1f PT - %s",i,grade.grade,points,grade.details) -if grade.wire and grade.wire<=4 then -text=text..string.format(" %d-wire",grade.wire) -end -if grade.Tgroove and grade.Tgroove<=360 then -text=text..string.format(" Tgroove=%.1f s",grade.Tgroove) -end -end -if grade.finalscore then -p=p+grade.finalscore -n=n+1 -end -m=m+1 -end -end -if n>0 then -text=text..string.format("\nAverage points = %.1f",p/n) -else -text=text..string.format("\nNo data available.") -end -if playerData.client then -MESSAGE:New(text,30,nil,true):ToClient(playerData.client) -end -end -end -end -function AIRBOSS:_DisplayDebriefing(_unitName) -self:F(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local text=string.format("Debriefing:") -if#playerData.lastdebrief>0 then -text=text..string.format("\n================================\n") -for _,_data in pairs(playerData.lastdebrief)do -local step=_data.step -local comment=_data.hint -text=text..string.format("* %s:",step) -text=text..string.format("%s\n",comment) -end -else -text=text.." Nothing to show yet." -end -self:MessageToPlayer(playerData,text,nil,"",30,true) -end -end -end -function AIRBOSS:_DisplayQueue(_unitname,qname) -local unit,playername=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local playerData=self.players[playername] -if playerData then -local queue=nil -if qname=="Marshal"then -queue=self.Qmarshal -elseif qname=="Pattern"then -queue=self.Qpattern -elseif qname=="Waiting"then -queue=self.Qwaiting -end -local Nqueue,nqueue=self:_GetQueueInfo(queue,playerData.case) -local text=string.format("%s Queue:",qname) -if#queue==0 then -text=text.." empty" -else -local N=0 -if qname=="Marshal"then -for i,_flight in pairs(queue)do -local flight=_flight -local charlie=self:_GetCharlieTime(flight) -local Charlie=UTILS.SecondsToClock(charlie) -local stack=flight.flag -local angels=self:_GetAngels(self:_GetMarshalAltitude(stack,flight.case)) -local _,nunit,nsec=self:_GetFlightUnits(flight,true) -local nick=self:_GetACNickname(flight.actype) -N=N+nunit -text=text..string.format("\n[Stack %d] %s (%s*%d+%d): Case %d, Angels %d, Charlie %s",stack,flight.onboard,nick,nunit,nsec,flight.case,angels,tostring(Charlie)) -end -elseif qname=="Pattern"or qname=="Waiting"then -for i,_flight in pairs(queue)do -local flight=_flight -local _,nunit,nsec=self:_GetFlightUnits(flight,true) -local nick=self:_GetACNickname(flight.actype) -local ptime=UTILS.SecondsToClock(timer.getAbsTime()-flight.time) -N=N+nunit -text=text..string.format("\n[%d] %s (%s*%d+%d): Case %d, T=%s",i,flight.onboard,nick,nunit,nsec,flight.case,ptime) -end -end -text=text..string.format("\nTotal AC: %d (airborne %d)",N,nqueue) -end -self:MessageToPlayer(playerData,text,nil,"",nil,true) -end -end -end -function AIRBOSS:_DisplayCarrierInfo(_unitname) -self:F2(_unitname) -local unit,playername=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local playerData=self.players[playername] -if playerData then -local coord=self:GetCoordinate() -local carrierheading=self.carrier:GetHeading() -local carrierspeed=UTILS.MpsToKnots(self.carrier:GetVelocityMPS()) -local tacan="unknown" -local icls="unknown" -if self.TACANon and self.TACANchannel~=nil then -tacan=string.format("%d%s (%s)",self.TACANchannel,self.TACANmode,self.TACANmorse) -end -if self.ICLSon and self.ICLSchannel~=nil then -icls=string.format("%d (%s)",self.ICLSchannel,self.ICLSmorse) -end -local wind=UTILS.MpsToKnots(select(1,self:GetWindOnDeck())) -local Nmarshal,nmarshal=self:_GetQueueInfo(self.Qmarshal,playerData.case) -local Npattern,npattern=self:_GetQueueInfo(self.Qpattern) -local Nspinning,nspinning=self:_GetQueueInfo(self.Qspinning) -local Nwaiting,nwaiting=self:_GetQueueInfo(self.Qwaiting) -local Ntotal,ntotal=self:_GetQueueInfo(self.flights) -local Tabs=timer.getAbsTime() -local recoverytext="Recovery time windows (max 5):" -if#self.recoverytimes==0 then -recoverytext=recoverytext.." none." -else -local rw=0 -for _,_recovery in pairs(self.recoverytimes)do -local recovery=_recovery -if Tabs=5 then -break -end -end -end -end -local tankertext=nil -if self.tanker then -tankertext=string.format("Recovery tanker frequency %.3f MHz\n",self.tanker.RadioFreq) -if self.tanker.TACANon then -tankertext=tankertext..string.format("Recovery tanker TACAN %d%s (%s)",self.tanker.TACANchannel,self.tanker.TACANmode,self.tanker.TACANmorse) -else -tankertext=tankertext.."Recovery tanker TACAN n/a" -end -end -local state=self:GetState() -if state=="Idle"then -state="Deck closed" -end -if self.turning then -state=state.." (currently turning)" -end -local text=string.format("%s info:\n",self.alias) -text=text..string.format("================================\n") -text=text..string.format("Carrier state: %s\n",state) -if self.case==1 then -text=text..string.format("Case %d recovery ops\n",self.case) -else -local radial=self:GetRadial(self.case,true,true,false) -text=text..string.format("Case %d recovery ops\nMarshal radial %03d°\n",self.case,radial) -end -text=text..string.format("BRC %03d° - FB %03d°\n",self:GetBRC(),self:GetFinalBearing(true)) -text=text..string.format("Speed %.1f kts - Wind on deck %.1f kts\n",carrierspeed,wind) -text=text..string.format("Tower frequency %.3f MHz\n",self.TowerFreq) -text=text..string.format("Marshal radio %.3f MHz\n",self.MarshalFreq) -text=text..string.format("LSO radio %.3f MHz\n",self.LSOFreq) -text=text..string.format("TACAN Channel %s\n",tacan) -text=text..string.format("ICLS Channel %s\n",icls) -if tankertext then -text=text..tankertext.."\n" -end -text=text..string.format("# A/C total %d (%d)\n",Ntotal,ntotal) -text=text..string.format("# A/C marshal %d (%d)\n",Nmarshal,nmarshal) -text=text..string.format("# A/C pattern %d (%d) - spinning %d (%d)\n",Npattern,npattern,Nspinning,nspinning) -text=text..string.format("# A/C waiting %d (%d)\n",Nwaiting,nwaiting) -text=text..string.format(recoverytext) -self:T2(self.lid..text) -self:MessageToPlayer(playerData,text,nil,"",30,true) -else -self:E(self.lid..string.format("ERROR: Could not get player data for player %s.",playername)) -end -end -end -function AIRBOSS:_DisplayCarrierWeather(_unitname) -self:F2(_unitname) -local unit,playername=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local text="" -local coord=self:GetCoordinate() -local T=coord:GetTemperature() -local P=coord:GetPressure() -local Wd,Ws=self:GetWind(nil,true) -local Bn,Bd=UTILS.BeaufortScale(Ws) -local WodPA,WodPP=self:GetWindOnDeck() -local WodPA=UTILS.MpsToKnots(WodPA) -local WodPP=UTILS.MpsToKnots(WodPP) -local WD=string.format('%03d°',Wd) -local Ts=string.format("%d°C",T) -local tT=string.format("%d°C",T) -local tW=string.format("%.1f knots",UTILS.MpsToKnots(Ws)) -local tP=string.format("%.2f inHg",UTILS.hPa2inHg(P)) -text=text..string.format("Weather Report at Carrier %s:\n",self.alias) -text=text..string.format("================================\n") -text=text..string.format("Temperature %s\n",tT) -text=text..string.format("Wind from %s at %s (%s)\n",WD,tW,Bd) -text=text..string.format("Wind on deck || %.1f kts, == %.1f kts\n",WodPA,WodPP) -text=text..string.format("QFE %.1f hPa = %s",P,tP) -if self.staticweather then -local clouds,visibility,fog,dust=self:_GetStaticWeather() -text=text..string.format("\nVisibility %.1f NM",UTILS.MetersToNM(visibility)) -text=text..string.format("\nCloud base %d ft",UTILS.MetersToFeet(clouds.base)) -text=text..string.format("\nCloud thickness %d ft",UTILS.MetersToFeet(clouds.thickness)) -text=text..string.format("\nCloud density %d",clouds.density) -text=text..string.format("\nPrecipitation %d",clouds.iprecptns) -if fog then -text=text..string.format("\nFog thickness %d ft",UTILS.MetersToFeet(fog.thickness)) -text=text..string.format("\nFog visibility %d ft",UTILS.MetersToFeet(fog.visibility)) -else -text=text..string.format("\nNo fog") -end -if dust then -text=text..string.format("\nDust density %d",dust) -else -text=text..string.format("\nNo dust") -end -end -self:T2(self.lid..text) -self:MessageToPlayer(self.players[playername],text,nil,"",30,true) -else -self:E(self.lid..string.format("ERROR! Could not find player unit in CarrierWeather! Unit name = %s",_unitname)) -end -end -function AIRBOSS:_SetDifficulty(_unitname,difficulty) -self:T2({difficulty=difficulty,unitname=_unitname}) -local unit,playername=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local playerData=self.players[playername] -if playerData then -playerData.difficulty=difficulty -local text=string.format("roger, your skill level is now: %s.",difficulty) -self:MessageToPlayer(playerData,text,nil,playerData.name,5) -else -self:E(self.lid..string.format("ERROR: Could not get player data for player %s.",playername)) -end -if playerData.difficulty==AIRBOSS.Difficulty.HARD then -playerData.showhints=false -else -playerData.showhints=true -end -end -end -function AIRBOSS:_SetHintsOnOff(_unitname) -self:F2(_unitname) -local unit,playername=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local playerData=self.players[playername] -if playerData then -playerData.showhints=not playerData.showhints -local text="" -if playerData.showhints==true then -text=string.format("roger, hints are now ON.") -else -text=string.format("affirm, hints are now OFF.") -end -self:MessageToPlayer(playerData,text,nil,playerData.name,5) -end -end -end -function AIRBOSS:_DisplayAttitude(_unitname) -self:F2(_unitname) -local unit,playername=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local playerData=self.players[playername] -if playerData then -playerData.attitudemonitor=not playerData.attitudemonitor -end -end -end -function AIRBOSS:_SubtitlesOnOff(_unitname) -self:F2(_unitname) -local unit,playername=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local playerData=self.players[playername] -if playerData then -playerData.subtitles=not playerData.subtitles -local text="" -if playerData.subtitles==true then -text=string.format("roger, subtitiles are now ON.") -elseif playerData.subtitles==false then -text=string.format("affirm, subtitiles are now OFF.") -end -self:MessageToPlayer(playerData,text,nil,playerData.name,5) -end -end -end -function AIRBOSS:_TrapsheetOnOff(_unitname) -self:F2(_unitname) -local unit,playername=self:_GetPlayerUnitAndName(_unitname) -if unit and playername then -local playerData=self.players[playername] -if playerData then -local text="" -if self.trapsheet then -playerData.trapon=not playerData.trapon -if playerData.trapon==true then -text=string.format("roger, your trapsheets are now SAVED.") -else -text=string.format("affirm, your trapsheets are NOT SAVED.") -end -else -text="negative, trap sheet data recorder is broken on this carrier." -end -self:MessageToPlayer(playerData,text,nil,playerData.name,5) -end -end -end -function AIRBOSS:_DisplayPlayerStatus(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local steptext=playerData.step -if playerData.step==AIRBOSS.PatternStep.HOLDING then -if playerData.holding==nil then -steptext="Transit to Marshal" -elseif playerData.holding==false then -steptext="Marshal (outside zone)" -elseif playerData.holding==true then -steptext="Marshal Stack Holding" -end -end -local stack=playerData.flag -local stacktext=nil -if stack>0 then -local stackalt=self:_GetMarshalAltitude(stack) -local angels=self:_GetAngels(stackalt) -stacktext=string.format("Marshal Stack %d, Angels %d\n",stack,angels) -if playerData.step==AIRBOSS.PatternStep.HOLDING and playerData.case>1 then -local radial=self:GetRadial(playerData.case,true,true,true) -stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n",radial,angels+15) -end -end -local fuel=playerData.unit:GetFuel()*100 -local fuelstate=self:_GetFuelState(playerData.unit) -local _,nunitsGround=self:_GetFlightUnits(playerData,true) -local _,nunitsAirborne=self:_GetFlightUnits(playerData,false) -local text=string.format("Status of player %s (%s)\n",playerData.name,playerData.callsign) -text=text..string.format("================================\n") -text=text..string.format("Step: %s\n",steptext) -if stacktext then -text=text..stacktext -end -text=text..string.format("Recovery Case: %d\n",playerData.case) -text=text..string.format("Skill Level: %s\n",playerData.difficulty) -text=text..string.format("Modex: %s (%s)\n",playerData.onboard,self:_GetACNickname(playerData.actype)) -text=text..string.format("Fuel State: %.1f lbs/1000 (%.1f %%)\n",fuelstate/1000,fuel) -text=text..string.format("# units: %d (%d airborne)\n",nunitsGround,nunitsAirborne) -text=text..string.format("Section Lead: %s (%d/%d)",tostring(playerData.seclead),#playerData.section+1,self.NmaxSection+1) -for _,_sec in pairs(playerData.section)do -local sec=_sec -text=text..string.format("\n- %s",sec.name) -end -if playerData.step==AIRBOSS.PatternStep.INITIAL then -local zoneinitial=self:GetCoordinate():Translate(UTILS.NMToMeters(3.5),self:GetRadial(2,false,false,false)) -local flyhdg=playerData.unit:GetCoordinate():HeadingTo(zoneinitial) -local flydist=UTILS.MetersToNM(playerData.unit:GetCoordinate():Get2DDistance(zoneinitial)) -local brc=self:GetBRC() -text=text..string.format("\nTo Initial: Fly heading %03d° for %.1f NM and turn to BRC %03d°",flyhdg,flydist,brc) -elseif playerData.step==AIRBOSS.PatternStep.PLATFORM then -local zoneplatform=self:_GetZonePlatform(playerData.case):GetCoordinate() -local flyhdg=playerData.unit:GetCoordinate():HeadingTo(zoneplatform) -local flydist=UTILS.MetersToNM(playerData.unit:GetCoordinate():Get2DDistance(zoneplatform)) -local hdg=self:GetRadial(playerData.case,true,true,true) -text=text..string.format("\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°",flyhdg,flydist,hdg) -end -self:MessageToPlayer(playerData,text,nil,"",30,true) -else -self:E(self.lid..string.format("ERROR: playerData=nil. Unit name=%s, player name=%s",_unitName,_playername)) -end -else -self:E(self.lid..string.format("ERROR: could not find player for unit %s",_unitName)) -end -end -function AIRBOSS:_MarkMarshalZone(_unitName,flare) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local stack=playerData.flag -local case=playerData.case -local text="" -if stack>0 then -local zoneHolding=self:_GetZoneHolding(case,stack) -local zoneThree=self:_GetZoneCommence(case,stack) -local patternalt=self:_GetMarshalAltitude(stack,case) -patternalt=5 -text="roger, marking" -if flare then -text=text..string.format("\n* Marshal zone stack %d with WHITE flares.",stack) -zoneHolding:FlareZone(FLARECOLOR.White,45,nil,patternalt) -text=text.."\n* Commence zone with RED flares." -zoneThree:FlareZone(FLARECOLOR.Red,45,nil,patternalt) -else -text=text..string.format("\n* Marshal zone stack %d with WHITE smoke.",stack) -zoneHolding:SmokeZone(SMOKECOLOR.White,45,patternalt) -text=text.."\n* Commence zone with RED smoke." -zoneThree:SmokeZone(SMOKECOLOR.Red,45,patternalt) -end -else -text="negative, you are currently not in a Marshal stack. No zones will be marked!" -end -self:MessageToPlayer(playerData,text,"MARSHAL",playerData.name) -end -end -end -function AIRBOSS:_MarkCaseZones(_unitName,flare) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -local case=playerData.case -local text=string.format("affirm, marking CASE %d zones",case) -if flare then -if case==1 or case==2 then -text=text.."\n* initial with GREEN flares" -self:_GetZoneInitial(case):FlareZone(FLARECOLOR.Green,45) -end -if case==2 or case==3 then -text=text.."\n* approach corridor with GREEN flares" -self:_GetZoneCorridor(case):FlareZone(FLARECOLOR.Green,45) -end -if case==2 or case==3 then -text=text.."\n* platform with RED flares" -self:_GetZonePlatform(case):FlareZone(FLARECOLOR.Red,45) -end -if case==3 then -text=text.."\n* dirty up with YELLOW flares" -self:_GetZoneDirtyUp(case):FlareZone(FLARECOLOR.Yellow,45) -end -if case==2 or case==3 then -if math.abs(self.holdingoffset)>0 then -self:_GetZoneArcIn(case):FlareZone(FLARECOLOR.White,45) -text=text.."\n* arc turn in with WHITE flares" -self:_GetZoneArcOut(case):FlareZone(FLARECOLOR.White,45) -text=text.."\n* arc trun out with WHITE flares" -end -end -if case==3 then -text=text.."\n* bullseye with GREEN flares" -self:_GetZoneBullseye(case):FlareZone(FLARECOLOR.Green,45) -end -if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -text=text.."\n* abeam landing stop with RED flares" -local ALSPT=self:_GetZoneAbeamLandingSpot() -ALSPT:FlareZone(FLARECOLOR.Red,5,nil,UTILS.FeetToMeters(110)) -text=text.."\n* primary landing spot with GREEN flares" -local LSPT=self:_GetZoneLandingSpot() -LSPT:FlareZone(FLARECOLOR.Green,5,nil,self.carrierparam.deckheight) -end -else -if case==1 or case==2 then -text=text.."\n* initial with GREEN smoke" -self:_GetZoneInitial(case):SmokeZone(SMOKECOLOR.Green,45) -end -if case==2 or case==3 then -text=text.."\n* approach corridor with GREEN smoke" -self:_GetZoneCorridor(case):SmokeZone(SMOKECOLOR.Green,45) -end -if case==2 or case==3 then -text=text.."\n* platform with RED smoke" -self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Red,45) -end -if case==2 or case==3 then -if math.abs(self.holdingoffset)>0 then -self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue,45) -text=text.."\n* arc turn in with BLUE smoke" -self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue,45) -text=text.."\n* arc trun out with BLUE smoke" -end -end -if case==3 then -text=text.."\n* dirty up with ORANGE smoke" -self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange,45) -end -if case==3 then -text=text.."\n* bullseye with GREEN smoke" -self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.Green,45) -end -end -self:MessageToPlayer(playerData,text,"MARSHAL",playerData.name) -end -end -end -function AIRBOSS:_LSORadioCheck(_unitName) -self:F(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -self:RadioTransmission(self.LSORadio,self.LSOCall.RADIOCHECK,nil,nil,nil,true) -end -end -end -function AIRBOSS:_MarshalRadioCheck(_unitName) -self:F(_unitName) -local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) -if _unit and _playername then -local playerData=self.players[_playername] -if playerData then -self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RADIOCHECK,nil,nil,nil,true) -end -end -end -function AIRBOSS:_SaveTrapSheet(playerData,grade) -if playerData.trapsheet==nil or#playerData.trapsheet==0 or not io then -return -end -local function _savefile(filename,data) -local f=io.open(filename,"wb") -if f then -f:write(data) -f:close() -else -self:E(self.lid..string.format("ERROR: could not save trap sheet to file %s.\nFile may contain invalid characters.",tostring(filename))) -end -end -local path=self.trappath -if lfs then -path=path or lfs.writedir() -end -local filename=nil -for i=1,9999 do -if self.trapprefix then -filename=string.format("%s_%s-%04d.csv",self.trapprefix,playerData.actype,i) -else -local name=UTILS.ReplaceIllegalCharacters(playerData.name,"_") -filename=string.format("AIRBOSS-%s_Trapsheet-%s_%s-%04d.csv",self.alias,name,playerData.actype,i) -end -if path~=nil then -filename=path.."\\"..filename -end -local _exists=UTILS.FileExists(filename) -if not _exists then -break -end -end -local text=string.format("Saving player %s trapsheet to file %s",playerData.name,filename) -self:I(self.lid..text) -local data="#Time,Rho,X,Z,Alt,AoA,GSE,LUE,Vtot,Vy,Gamma,Pitch,Roll,Yaw,Step,Grade,Points,Details\n" -local g0=playerData.trapsheet[1] -local T0=g0.Time -for i=1,#playerData.trapsheet do -local groove=playerData.trapsheet[i] -local t=groove.Time-T0 -local a=UTILS.MetersToNM(groove.Rho or 0) -local b=-groove.X or 0 -local c=groove.Z or 0 -local d=UTILS.MetersToFeet(groove.Alt or 0) -local e=groove.AoA or 0 -local f=groove.GSE or 0 -local g=-groove.LUE or 0 -local h=UTILS.MpsToKnots(groove.Vel or 0) -local i=(groove.Vy or 0)*196.85 -local j=groove.Gamma or 0 -local k=groove.Pitch or 0 -local l=groove.Roll or 0 -local m=groove.Yaw or 0 -local n=self:_GS(groove.Step,-1)or"n/a" -local o=groove.Grade or"n/a" -local p=groove.GradePoints or 0 -local q=groove.GradeDetail or"n/a" -data=data..string.format("%.2f,%.3f,%.1f,%.1f,%.1f,%.2f,%.2f,%.2f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%s,%s,%.1f,%s\n",t,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q) -end -_savefile(filename,data) -end -function AIRBOSS:onbeforeSave(From,Event,To,path,filename) -if not io then -self:E(self.lid.."ERROR: io not desanitized. Can't save player grades.") -return false -end -if path==nil and not lfs then -self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") -end -return true -end -function AIRBOSS:onafterSave(From,Event,To,path,filename) -local function _savefile(filename,data) -local f=assert(io.open(filename,"wb")) -f:write(data) -f:close() -end -if lfs then -path=path or lfs.writedir() -end -filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias) -if path~=nil then -filename=path.."\\"..filename -end -local scores="Name,Pass,Points Final,Points Pass,Grade,Details,Wire,Tgroove,Case,Wind,Modex,Airframe,Carrier Type,Carrier Name,Theatre,Mission Time,Mission Date,OS Date\n" -local n=0 -for playername,grades in pairs(self.playerscores)do -for i,_grade in pairs(grades)do -local grade=_grade -local wire="n/a" -if grade.wire and grade.wire<=4 then -wire=tostring(grade.wire) -end -local Tgroove="n/a" -if grade.Tgroove and grade.Tgroove<=360 and grade.case<3 then -Tgroove=tostring(UTILS.Round(grade.Tgroove,1)) -end -local finalscore="n/a" -if grade.finalscore then -finalscore=tostring(UTILS.Round(grade.finalscore,1)) -end -scores=scores..string.format("%s,%d,%s,%.1f,%s,%s,%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",playername,i,finalscore,grade.points,grade.grade,grade.details,wire,Tgroove,grade.case,grade.wind,grade.modex,grade.airframe,grade.carriertype,grade.carriername,grade.theatre,grade.mitime,grade.midate,grade.osdate) -n=n+1 -end -end -local text=string.format("Saving %d player LSO grades to file %s",n,filename) -self:I(self.lid..text) -_savefile(filename,scores) -end -function AIRBOSS:onbeforeLoad(From,Event,To,path,filename) -local function _fileexists(name) -local f=io.open(name,"r") -if f~=nil then -io.close(f) -return true -else -return false -end -end -if not io then -self:E(self.lid.."WARNING: io not desanitized. Can't load player grades.") -return false -end -if path==nil and not lfs then -self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") -end -if lfs then -path=path or lfs.writedir() -end -filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias) -if path~=nil then -filename=path.."\\"..filename -end -local exists=_fileexists(filename) -if exists then -return true -else -self:E(self.lid..string.format("WARNING: Player LSO grades file %s does not exist.",filename)) -return false -end -end -function AIRBOSS:onafterLoad(From,Event,To,path,filename) -local function _loadfile(filename) -local f=assert(io.open(filename,"rb")) -local data=f:read("*all") -f:close() -return data -end -if lfs then -path=path or lfs.writedir() -end -filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias) -if path~=nil then -filename=path.."\\"..filename -end -local text=string.format("Loading player LSO grades from file %s",filename) -MESSAGE:New(text,10):ToAllIf(self.Debug) -self:I(self.lid..text) -local data=_loadfile(filename) -local playergrades=UTILS.Split(data,"\n") -table.remove(playergrades,1) -self.playerscores={} -local n=0 -for _,gradeline in pairs(playergrades)do -local gradedata=UTILS.Split(gradeline,",") -self:T2(gradedata) -local grade={} -local playername=gradedata[1] -if gradedata[3]~=nil and gradedata[3]~="n/a"then -grade.finalscore=tonumber(gradedata[3]) -end -grade.points=tonumber(gradedata[4]) -grade.grade=tostring(gradedata[5]) -grade.details=tostring(gradedata[6]) -if gradedata[7]~=nil and gradedata[7]~="n/a"then -grade.wire=tonumber(gradedata[7]) -end -if gradedata[8]~=nil and gradedata[8]~="n/a"then -grade.Tgroove=tonumber(gradedata[8]) -end -grade.case=tonumber(gradedata[9]) -grade.wind=gradedata[10]or"n/a" -grade.modex=gradedata[11]or"n/a" -grade.airframe=gradedata[12]or"n/a" -grade.carriertype=gradedata[13]or"n/a" -grade.carriername=gradedata[14]or"n/a" -grade.theatre=gradedata[15]or"n/a" -grade.mitime=gradedata[16]or"n/a" -grade.midate=gradedata[17]or"n/a" -grade.osdate=gradedata[18]or"n/a" -self.playerscores[playername]=self.playerscores[playername]or{} -table.insert(self.playerscores[playername],grade) -n=n+1 -self:T2({playername,self.playerscores[playername]}) -end -local text=string.format("Loaded %d player LSO grades from file %s",n,filename) -self:I(self.lid..text) -end -function AIRBOSS:onafterLSOGrade(From,Event,To,playerData,grade) -if self.funkmanSocket then -local trapsheet={};trapsheet.X={};trapsheet.Z={};trapsheet.AoA={};trapsheet.Alt={} -for i=1,#playerData.trapsheet do -local ts=playerData.trapsheet[i] -table.insert(trapsheet.X,UTILS.Round(ts.X,1)) -table.insert(trapsheet.Z,UTILS.Round(ts.Z,1)) -table.insert(trapsheet.AoA,UTILS.Round(ts.AoA,2)) -table.insert(trapsheet.Alt,UTILS.Round(ts.Alt,1)) -end -local result={} -result.command=SOCKET.DataType.LSOGRADE -result.name=playerData.name -result.trapsheet=trapsheet -result.airframe=grade.airframe -result.mitime=grade.mitime -result.midate=grade.midate -result.wind=grade.wind -result.carriertype=grade.carriertype -result.carriername=grade.carriername -result.carrierrwy=grade.carrierrwy -result.landingdist=self.carrierparam.landingdist -result.theatre=grade.theatre -result.case=playerData.case -result.Tgroove=grade.Tgroove -result.wire=grade.wire -result.grade=grade.grade -result.points=grade.points -result.details=grade.details -self:T(self.lid.."Result onafterLSOGrade") -self:T(result) -self.funkmanSocket:SendTable(result) -end -end -RECOVERYTANKER={ -ClassName="RECOVERYTANKER", -Debug=false, -lid=nil, -carrier=nil, -carriertype=nil, -tankergroupname=nil, -tanker=nil, -airbase=nil, -beacon=nil, -TACANchannel=nil, -TACANmode=nil, -TACANmorse=nil, -TACANon=nil, -RadioFreq=nil, -RadioModu=nil, -altitude=nil, -speed=nil, -distStern=nil, -distBow=nil, -dTupdate=nil, -Dupdate=nil, -Hupdate=nil, -Tupdate=nil, -takeoff=nil, -lowfuel=nil, -respawn=nil, -respawninair=nil, -uncontrolledac=nil, -orientation=nil, -orientlast=nil, -position=nil, -alias=nil, -uid=0, -awacs=nil, -callsignname=nil, -callsignnumber=nil, -modex=nil, -eplrs=nil, -recovery=nil, -terminaltype=nil, -unlimitedfuel=false, -} -_RECOVERYTANKERID=0 -RECOVERYTANKER.version="1.0.10" -function RECOVERYTANKER:New(carrierunit,tankergroupname) -local self=BASE:Inherit(self,FSM:New()) -if type(carrierunit)=="string"then -self.carrier=UNIT:FindByName(carrierunit) -else -self.carrier=carrierunit -end -self.carriertype=self.carrier:GetTypeName() -self.tankergroupname=tankergroupname -_RECOVERYTANKERID=_RECOVERYTANKERID+1 -self.uid=_RECOVERYTANKERID -self.carrier:SetState(self.carrier,string.format("RECOVERYTANKER_%d",self.uid),self) -self.alias=string.format("%s_%s_%02d",self.carrier:GetName(),self.tankergroupname,_RECOVERYTANKERID) -self.lid=string.format("RECOVERYTANKER %s | ",self.alias) -self:SetAltitude() -self:SetSpeed() -self:SetRacetrackDistances() -self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName())) -self:SetTakeoffHot() -self:SetLowFuelThreshold() -self:SetRespawnOnOff() -self:SetTACAN() -self:SetRadio() -self:SetPatternUpdateDistance() -self:SetPatternUpdateHeading() -self:SetPatternUpdateInterval() -self:SetAWACS(false) -self:SetRecoveryAirboss(false) -self.terminaltype=AIRBASE.TerminalType.OpenMedOrBig -if false then -BASE:TraceOnOff(true) -BASE:TraceClass(self.ClassName) -BASE:TraceLevel(1) -end -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","RefuelStart","Refueling") -self:AddTransition("*","RefuelStop","Running") -self:AddTransition("*","Run","Running") -self:AddTransition("Running","RTB","Returning") -self:AddTransition("Returning","Returned","Returned") -self:AddTransition("*","Status","*") -self:AddTransition("Running","PatternUpdate","*") -self:AddTransition("*","Stop","Stopped") -return self -end -function RECOVERYTANKER:SetUnlimitedFuel(OnOff) -self.unlimitedfuel=OnOff -return self -end -function RECOVERYTANKER:SetSpeed(speed) -self.speed=UTILS.KnotsToMps(speed or 274) -return self -end -function RECOVERYTANKER:SetAltitude(altitude) -self.altitude=UTILS.FeetToMeters(altitude or 6000) -return self -end -function RECOVERYTANKER:SetRacetrackDistances(distbow,diststern) -self.distBow=UTILS.NMToMeters(distbow or 10) -self.distStern=-UTILS.NMToMeters(diststern or 4) -return self -end -function RECOVERYTANKER:SetPatternUpdateInterval(interval) -self.dTupdate=(interval or 10)*60 -return self -end -function RECOVERYTANKER:SetPatternUpdateDistance(distancechange) -self.Dupdate=UTILS.NMToMeters(distancechange or 5) -return self -end -function RECOVERYTANKER:SetPatternUpdateHeading(headingchange) -self.Hupdate=headingchange or 5 -return self -end -function RECOVERYTANKER:SetLowFuelThreshold(fuelthreshold) -self.lowfuel=fuelthreshold or 10 -return self -end -function RECOVERYTANKER:SetHomeBase(airbase,terminaltype) -if type(airbase)=="string"then -self.airbase=AIRBASE:FindByName(airbase) -else -self.airbase=airbase -end -if not self.airbase then -self:E(self.lid.."ERROR: Airbase is nil!") -end -if terminaltype then -self.terminaltype=terminaltype -end -return self -end -function RECOVERYTANKER:SetRecoveryAirboss(switch) -if switch==true or switch==nil then -self.recovery=true -else -self.recovery=false -end -return self -end -function RECOVERYTANKER:SetAWACS(switch,eplrs) -if switch==nil or switch==true then -self.awacs=true -else -self.awacs=false -end -if eplrs==nil or eplrs==true then -self.eplrs=true -else -self.eplrs=false -end -return self -end -function RECOVERYTANKER:SetCallsign(callsignname,callsignnumber) -self.callsignname=callsignname -self.callsignnumber=callsignnumber -return self -end -function RECOVERYTANKER:SetModex(modex) -self.modex=modex -return self -end -function RECOVERYTANKER:SetTakeoff(takeofftype) -self.takeoff=takeofftype -return self -end -function RECOVERYTANKER:SetTakeoffHot() -self:SetTakeoff(SPAWN.Takeoff.Hot) -return self -end -function RECOVERYTANKER:SetTakeoffCold() -self:SetTakeoff(SPAWN.Takeoff.Cold) -return self -end -function RECOVERYTANKER:SetTakeoffAir() -self:SetTakeoff(SPAWN.Takeoff.Air) -return self -end -function RECOVERYTANKER:SetRespawnOn() -self.respawn=true -return self -end -function RECOVERYTANKER:SetRespawnOff() -self.respawn=false -return self -end -function RECOVERYTANKER:SetRespawnOnOff(switch) -if switch==nil or switch==true then -self.respawn=true -else -self.respawn=false -end -return self -end -function RECOVERYTANKER:SetRespawnInAir() -self.respawninair=true -return self -end -function RECOVERYTANKER:SetUseUncontrolledAircraft() -self.uncontrolledac=true -return self -end -function RECOVERYTANKER:SetTACANoff() -self.TACANon=false -return self -end -function RECOVERYTANKER:SetTACAN(channel,morse,mode) -self.TACANchannel=channel or 1 -self.TACANmode=mode or"Y" -self.TACANmorse=morse or"TKR" -self.TACANon=true -return self -end -function RECOVERYTANKER:SetRadio(frequency,modulation) -self.RadioFreq=frequency or 251 -self.RadioModu=modulation or"AM" -return self -end -function RECOVERYTANKER:SetDebugModeON() -self.Debug=true -return self -end -function RECOVERYTANKER:SetDebugModeOFF() -self.Debug=false -return self -end -function RECOVERYTANKER:IsReturning() -return self:is("Returning") -end -function RECOVERYTANKER:IsReturned() -return self:is("Returned") -end -function RECOVERYTANKER:IsRunning() -return self:is("Running") -end -function RECOVERYTANKER:IsRefueling() -return self:is("Refueling") -end -function RECOVERYTANKER:IsStopped() -return self:is("Stopped") -end -function RECOVERYTANKER:GetAlias() -return self.alias -end -function RECOVERYTANKER:GetUnitName() -local unit=self.tanker:GetUnit(1) -if unit then -return unit:GetName() -end -return nil -end -function RECOVERYTANKER:onafterStart(From,Event,To) -self:I(string.format("Starting Recovery Tanker v%s for carrier unit %s of type %s for tanker group %s.",RECOVERYTANKER.version,self.carrier:GetName(),self.carriertype,self.tankergroupname)) -self:HandleEvent(EVENTS.EngineShutdown) -self:HandleEvent(EVENTS.Land) -self:HandleEvent(EVENTS.Refueling,self._RefuelingStart) -self:HandleEvent(EVENTS.RefuelingStop,self._RefuelingStop) -self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrDead) -self:HandleEvent(EVENTS.Dead,self._OnEventCrashOrDead) -local Spawn=SPAWN:NewWithAlias(self.tankergroupname,self.alias) -if self.unlimitedfuel then -Spawn:OnSpawnGroup( -function(grp) -grp:CommandSetUnlimitedFuel(self.unlimitedfuel) -end -) -end -Spawn:InitRadioCommsOnOff(true) -Spawn:InitRadioFrequency(self.RadioFreq) -Spawn:InitRadioModulation(self.RadioModu) -Spawn:InitModex(self.modex) -if self.takeoff==SPAWN.Takeoff.Air then -local hdg=self.carrier:GetHeading() -local dist=-self.distStern+UTILS.NMToMeters(4) -local Carrier=self.carrier:GetCoordinate():Translate(dist,hdg+190):SetAltitude(self.altitude) -Spawn:InitHeading(hdg+10) -self.tanker=Spawn:SpawnFromCoordinate(Carrier) -else -if self.uncontrolledac then -self.tanker=GROUP:FindByName(self.tankergroupname) -if self.tanker:IsAlive()then -self.tanker:StartUncontrolled() -else -self:E(string.format("ERROR: No uncontrolled (alive) tanker group with name %s could be found!",self.tankergroupname)) -return -end -else -self.tanker=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,self.terminaltype) -end -end -self:ScheduleOnce(1,self._InitRoute,self,-self.distStern+UTILS.NMToMeters(3)) -if self.TACANon then -self:_ActivateTACAN(2) -end -if self.callsignname then -self.tanker:CommandSetCallsign(self.callsignname,self.callsignnumber,2) -end -if self.eplrs then -self.tanker:CommandEPLRS(true,3) -end -self.orientation=self.carrier:GetOrientationX() -self.orientlast=self.carrier:GetOrientationX() -self.position=self.carrier:GetCoordinate() -self:__Status(10) -end -function RECOVERYTANKER:onafterStatus(From,Event,To) -local time=timer.getTime() -if self.tanker and self.tanker:IsAlive()then -local fuel=self.tanker:GetFuel()*100 -local life=self.tanker:GetUnit(1):GetLife() -local life0=self.tanker:GetUnit(1):GetLife0() -local lifeR=self.tanker:GetUnit(1):GetLifeRelative() -local text=string.format("Recovery tanker %s: state=%s fuel=%.1f, life=%.1f/%.1f=%d",self.tanker:GetName(),self:GetState(),fuel,life,life0,lifeR*100) -self:T(self.lid..text) -MESSAGE:New(text,10):ToAllIf(self.Debug) -if self:IsRunning()then -if fuel100 then -return -end -local text=string.format("Recovery tanker %s started refueling unit %s",self.tanker:GetName(),receiver:GetName()) -MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) -self:T(self.lid..text) -self:RefuelStart(receiver) -end -end -function RECOVERYTANKER:_RefuelingStop(EventData) -if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive()then -local receiver=EventData.IniUnit -local dist=receiver:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate()) -if dist>100 then -return -end -local text=string.format("Recovery tanker %s stopped refueling unit %s",self.tanker:GetName(),receiver:GetName()) -MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) -self:T(self.lid..text) -self:RefuelStop(receiver) -end -end -function RECOVERYTANKER:_OnEventCrashOrDead(EventData) -self:F2({eventdata=EventData}) -if EventData and EventData.IniUnit then -local unit=EventData.IniUnit -local unitname=tostring(EventData.IniUnitName) -if EventData.IniGroupName==self.tanker:GetName()then -self:E(self.lid..string.format("Recovery tanker %s crashed!",unitname)) -self:Stop() -if self.respawn then -self:__Start(5) -end -end -end -end -function RECOVERYTANKER:_InitPatternTaskFunction() -local carriername=self.carrier:GetName() -local DCSScript={} -DCSScript[#DCSScript+1]=string.format('local mycarrier = UNIT:FindByName(\"%s\") ',carriername) -DCSScript[#DCSScript+1]=string.format('local mytanker = mycarrier:GetState(mycarrier, \"RECOVERYTANKER_%d\") ',self.uid) -DCSScript[#DCSScript+1]=string.format('mytanker:PatternUpdate()') -local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript))) -return DCSTask -end -function RECOVERYTANKER:_InitRoute(dist,delay) -dist=dist or UTILS.NMToMeters(8) -delay=delay or 1 -self:T(self.lid..string.format("Initializing route of recovery tanker %s.",self.tanker:GetName())) -local Carrier=self.carrier:GetCoordinate() -local hdg=self.carrier:GetHeading() -local p=Carrier:Translate(dist,hdg+190):SetAltitude(self.altitude) -local speed=self.tanker:GetSpeedMax()*0.8 -if self.Debug then -p:MarkToAll(string.format("Enter Pattern WP: alt=%d ft, speed=%d kts",UTILS.MetersToFeet(self.altitude),speed*0.539957)) -end -local task=self:_InitPatternTaskFunction() -local wp={} -if self.takeoff==SPAWN.Takeoff.Air then -wp[#wp+1]=self.tanker:GetCoordinate():SetAltitude(self.altitude):WaypointAirTurningPoint(nil,speed,{},"Spawn Position") -else -wp[#wp+1]=Carrier:WaypointAirTakeOffParking() -end -wp[#wp+1]=p:WaypointAirTurningPoint(nil,speed,{task},"Enter Pattern") -self.tanker:Route(wp,delay) -self:__Run(1) -self.Tupdate=nil -end -function RECOVERYTANKER:_CheckPatternUpdate(dt) -local pos=self.carrier:GetCoordinate() -local vNew=self.carrier:GetOrientationX() -local vOld=self.orientation -local vLast=self.orientlast -vNew.y=0;vOld.y=0;vLast.y=0 -local deltaHeading=math.deg(math.acos(UTILS.VecDot(vNew,vOld)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vOld))) -local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast))) -self.orientlast=vNew -local turning=deltaLast>=1 -if turning then -self:T2(self.lid..string.format("Carrier is turning. Delta Heading = %.1f",deltaLast)) -end -local Hchange=false -if math.abs(deltaHeading)>=self.Hupdate then -self:T(self.lid..string.format("Carrier heading changed by %d degrees. Turning=%s.",deltaHeading,tostring(turning))) -Hchange=true -end -local dist=pos:Get2DDistance(self.position) -local Dchange=false -if dist>self.Dupdate then -self:T(self.lid..string.format("Carrier position changed by %.1f NM. Turning=%s.",UTILS.MetersToNM(dist),tostring(turning))) -Dchange=true -end -local update=false -if self:IsRunning()and dt>self.dTupdate and not turning then -if Hchange or Dchange then -local text=string.format("Updating tanker %s pattern due to carrier position=%s or heading=%s change.",self.tanker:GetName(),tostring(Dchange),tostring(Hchange)) -MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) -self:T(self.lid..text) -self.orientation=vNew -self.position=pos -update=true -end -end -return update -end -function RECOVERYTANKER:_ActivateTACAN(delay) -if delay and delay>0 then -self:ScheduleOnce(delay,RECOVERYTANKER._ActivateTACAN,self) -else -local unit=self.tanker:GetUnit(1) -if unit and unit:IsAlive()then -local text=string.format("Activating TACAN beacon: channel=%d mode=%s, morse=%s.",self.TACANchannel,self.TACANmode,self.TACANmorse) -MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) -self:T(self.lid..text) -self.beacon=BEACON:New(unit) -self.beacon:ActivateTACAN(self.TACANchannel,self.TACANmode,self.TACANmorse,true) -else -self:E(self.lid.."ERROR: Recovery tanker is not alive!") -end -end -end -function RECOVERYTANKER:_Pattern() -local hdg=self.carrier:GetHeading() -local alt=self.altitude -local Carrier=self.carrier:GetCoordinate() -local width=UTILS.NMToMeters(8) -local p={} -p[1]=self.tanker:GetCoordinate() -p[2]=Carrier:SetAltitude(alt) -p[3]=p[2]:Translate(self.distBow,hdg) -p[4]=p[3]:Translate(width/math.sqrt(2),hdg-45) -p[5]=p[3]:Translate(width,hdg-90) -p[6]=p[5]:Translate(self.distStern-self.distBow,hdg) -p[7]=p[2]:Translate(self.distStern,hdg) -local wp={} -for i=1,#p do -local coord=p[i] -coord:MarkToAll(string.format("Waypoint %d",i)) -table.insert(wp,coord:WaypointAirTurningPoint(nil,UTILS.MpsToKmph(self.speed))) -end -return wp -end -RESCUEHELO={ -ClassName="RESCUEHELO", -Debug=false, -lid=nil, -carrier=nil, -carriertype=nil, -helogroupname=nil, -helo=nil, -airbase=nil, -takeoff=nil, -followset=nil, -formation=nil, -lowfuel=nil, -altitude=nil, -offsetX=nil, -offsetZ=nil, -rescuezone=nil, -respawn=nil, -respawninair=nil, -uncontrolledac=nil, -rescueon=nil, -rescueduration=nil, -rescuespeed=nil, -rescuestopboat=nil, -HeloFuel0=nil, -rtb=nil, -carrierstop=nil, -alias=nil, -uid=0, -modex=nil, -dtFollow=nil, -} -_RESCUEHELOID=0 -RESCUEHELO.version="1.1.0" -function RESCUEHELO:New(carrierunit,helogroupname) -local self=BASE:Inherit(self,FSM:New()) -if type(carrierunit)=="string"then -self.carrier=UNIT:FindByName(carrierunit) -else -self.carrier=carrierunit -end -self.carriertype=self.carrier:GetTypeName() -self.helogroupname=helogroupname -_RESCUEHELOID=_RESCUEHELOID+1 -self.uid=_RESCUEHELOID -self.carrier:SetState(self.carrier,string.format("RESCUEHELO_%d",self.uid),self) -self.alias=string.format("%s_%s_%02d",self.carrier:GetName(),self.helogroupname,_RESCUEHELOID) -self.lid=string.format("RESCUEHELO %s | ",self.alias) -self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName())) -self:SetTakeoffHot() -self:SetLowFuelThreshold() -self:SetAltitude() -self:SetOffsetX() -self:SetOffsetZ() -self:SetRespawnOn() -self:SetRescueOn() -self:SetRescueZone() -self:SetRescueHoverSpeed() -self:SetRescueDuration() -self:SetFollowTimeInterval() -self:SetRescueStopBoatOff() -self.rtb=false -self.carrierstop=false -if false then -self.Debug=true -BASE:TraceOnOff(true) -BASE:TraceClass(self.ClassName) -BASE:TraceLevel(1) -end -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("Running","Rescue","Rescuing") -self:AddTransition("Running","RTB","Returning") -self:AddTransition("Rescuing","RTB","Returning") -self:AddTransition("Returning","Returned","Returned") -self:AddTransition("Running","Run","Running") -self:AddTransition("Returned","Run","Running") -self:AddTransition("*","Status","*") -self:AddTransition("*","Stop","Stopped") -return self -end -function RESCUEHELO:SetLowFuelThreshold(threshold) -self.lowfuel=threshold or 5 -return self -end -function RESCUEHELO:SetHomeBase(airbase) -if type(airbase)=="string"then -self.airbase=AIRBASE:FindByName(airbase) -else -self.airbase=airbase -end -if not self.airbase then -self:E(self.lid.."ERROR: Airbase is nil!") -end -return self -end -function RESCUEHELO:SetRescueZone(radius) -radius=UTILS.NMToMeters(radius or 15) -self.rescuezone=ZONE_UNIT:New("Rescue Zone",self.carrier,radius) -return self -end -function RESCUEHELO:SetRescueHoverSpeed(speed) -self.rescuespeed=UTILS.KnotsToMps(speed or 5) -return self -end -function RESCUEHELO:SetRescueDuration(duration) -self.rescueduration=(duration or 5)*60 -return self -end -function RESCUEHELO:SetRescueOn() -self.rescueon=true -return self -end -function RESCUEHELO:SetRescueOff() -self.rescueon=false -return self -end -function RESCUEHELO:SetRescueStopBoatOn() -self.rescuestopboat=true -return self -end -function RESCUEHELO:SetRescueStopBoatOff() -self.rescuestopboat=false -return self -end -function RESCUEHELO:SetTakeoff(takeofftype) -self.takeoff=takeofftype or SPAWN.Takeoff.Hot -return self -end -function RESCUEHELO:SetTakeoffHot() -self:SetTakeoff(SPAWN.Takeoff.Hot) -return self -end -function RESCUEHELO:SetTakeoffCold() -self:SetTakeoff(SPAWN.Takeoff.Cold) -return self -end -function RESCUEHELO:SetTakeoffAir() -self:SetTakeoff(SPAWN.Takeoff.Air) -return self -end -function RESCUEHELO:SetAltitude(alt) -self.altitude=alt or 70 -return self -end -function RESCUEHELO:SetOffsetX(distance) -self.offsetX=distance or 200 -return self -end -function RESCUEHELO:SetOffsetZ(distance) -self.offsetZ=distance or 240 -return self -end -function RESCUEHELO:SetRespawnOn() -self.respawn=true -return self -end -function RESCUEHELO:SetRespawnOff() -self.respawn=false -return self -end -function RESCUEHELO:SetRespawnOnOff(switch) -if switch==nil or switch==true then -self.respawn=true -else -self.respawn=false -end -return self -end -function RESCUEHELO:SetRespawnInAir() -self.respawninair=true -return self -end -function RESCUEHELO:SetModex(modex) -self.modex=modex -return self -end -function RESCUEHELO:SetFollowTimeInterval(dt) -self.dtFollow=dt or 1.0 -return self -end -function RESCUEHELO:SetUseUncontrolledAircraft() -self.uncontrolledac=true -return self -end -function RESCUEHELO:SetDebugModeON() -self.Debug=true -return self -end -function RESCUEHELO:SetDebugModeOFF() -self.Debug=false -return self -end -function RESCUEHELO:IsReturning() -return self:is("Returning") -end -function RESCUEHELO:IsRunning() -return self:is("Running") -end -function RESCUEHELO:IsRescuing() -return self:is("Rescuing") -end -function RESCUEHELO:IsStopped() -return self:is("Stopped") -end -function RESCUEHELO:GetAlias() -return self.alias -end -function RESCUEHELO:GetUnitName() -local unit=self.helo:GetUnit(1) -if unit then -return unit:GetName() -end -return nil -end -function RESCUEHELO:OnEventLand(EventData) -local group=EventData.IniGroup -if group and group:IsAlive()then -local groupname=group:GetName() -if groupname==self.helo:GetName()then -local airbase=nil -local airbasename="unknown" -if EventData.Place then -airbase=EventData.Place -airbasename=airbase:GetName() -end -local text=string.format("Rescue helo group %s landed at airbase %s.",groupname,airbasename) -MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) -self:T(self.lid..text) -if self:IsRescuing()then -self:T(self.lid..string.format("Rescue helo %s returned from rescue operation.",groupname)) -end -if self.takeoff==SPAWN.Takeoff.Air or self.respawninair then -if not self:IsRescuing()then -self:E(self.lid..string.format("WARNING: Rescue helo %s landed. This should not happen for Takeoff=Air or respawninair=true and no rescue operation in progress.",groupname)) -end -end -self:__Returned(3,airbase) -end -end -end -function RESCUEHELO:_OnEventCrashOrEject(EventData) -self:F2({eventdata=EventData}) -if EventData and EventData.IniUnit then -local unit=EventData.IniUnit -local unitname=tostring(EventData.IniUnitName) -if EventData.IniGroupName~=self.helo:GetName()then -local text=string.format("Unit %s crashed or ejected.",unitname) -MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) -self:T(self.lid..text) -local Vec3=EventData.IniDCSUnit:getPoint() -local coord=COORDINATE:NewFromVec3(Vec3) -if coord and self.rescuezone:IsCoordinateInZone(coord)then -if self.Debug then -coord:MarkToCoalition(self.lid..string.format("Crash site of unit %s.",unitname),self.helo:GetCoalition()) -end -local rightcoalition=EventData.IniGroup:GetCoalition()==self.helo:GetCoalition() -if self:IsRunning()and self.rescueon and rightcoalition then -self:Rescue(coord) -end -end -else -self:E(self.lid..string.format("Rescue helo %s crashed!",unitname)) -self:Stop() -if self.respawn then -self:__Start(5) -end -end -end -end -function RESCUEHELO:onafterStart(From,Event,To) -local text=string.format("Starting Rescue Helo Formation v%s for carrier unit %s of type %s.",RESCUEHELO.version,self.carrier:GetName(),self.carriertype) -self:I(self.lid..text) -self:HandleEvent(EVENTS.Land) -self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrEject) -self:HandleEvent(EVENTS.Ejection,self._OnEventCrashOrEject) -local delay=120 -local Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias) -Spawn:InitModex(self.modex) -if self.takeoff==SPAWN.Takeoff.Air then -local hdg=self.carrier:GetHeading() -local dist=UTILS.NMToMeters(0.2) -local Carrier=self.carrier:GetCoordinate():Translate(dist,hdg):SetAltitude(math.max(100,self.altitude)) -Spawn:InitHeading(hdg) -self.helo=Spawn:SpawnFromCoordinate(Carrier) -delay=1 -else -if self.uncontrolledac then -self.helo=GROUP:FindByName(self.helogroupname) -if self.helo and self.helo:IsAlive()then -self.helo:StartUncontrolled() -delay=60 -else -self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname)) -return -end -else -self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable) -if self.takeoff==SPAWN.Takeoff.Runway then -delay=5 -elseif self.takeoff==SPAWN.Takeoff.Hot then -delay=30 -elseif self.takeoff==SPAWN.Takeoff.Cold then -delay=60 -end -end -end -self.followset=SET_GROUP:New() -self.followset:AddGroup(self.helo) -self.HeloFuel0=self.helo:GetFuel() -self.formation=AI_FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier","Follow Carrier at given parameters.") -self.formation:FormationCenterWing(-self.offsetX,50,math.abs(self.altitude),50,self.offsetZ,50) -self.formation:SetFollowTimeInterval(self.dtFollow) -self.formation:SetFlightModeFormation(self.helo) -self.formation:__Start(delay) -self:__Status(1) -end -function RESCUEHELO:onafterStatus(From,Event,To) -local time=timer.getTime() -if self.helo and self.helo:IsAlive()then -local fuel=self.helo:GetFuel()*100 -local fuelrel=fuel/self.HeloFuel0 -local life=self.helo:GetUnit(1):GetLife() -local life0=self.helo:GetUnit(1):GetLife0() -local lifeR=self.helo:GetUnit(1):GetLifeRelative() -local text=string.format("Rescue Helo %s: state=%s fuel=%.1f, rel.fuel=%.1f, life=%.1f/%.1f=%d",self.helo:GetName(),self:GetState(),fuel,fuelrel,life,life0,lifeR*100) -MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) -self:T(self.lid..text) -if self:IsRunning()then -if fuelUTILS.FeetToMeters(1500)then -dust=nil -end -local visibilitymin=visibility -if fog then -if fog.visibility10 then -reportedviz=10 -end -VISIBILITY=string.format("%d",reportedviz) -else -local reportedviz=UTILS.Round(UTILS.MetersToSM(visibilitymin)) -if reportedviz>10 then -reportedviz=10 -end -VISIBILITY=string.format("%d",reportedviz) -end -local cloudbase=clouds.base -local cloudceil=clouds.base+clouds.thickness -local clouddens=clouds.density -local cloudspreset=clouds.preset or"Nothing" -local precepitation=0 -if cloudspreset:find("Preset10")then -clouddens=4 -elseif cloudspreset:find("Preset11")then -clouddens=4 -elseif cloudspreset:find("Preset12")then -clouddens=4 -elseif cloudspreset:find("Preset13")then -clouddens=7 -elseif cloudspreset:find("Preset14")then -clouddens=7 -elseif cloudspreset:find("Preset15")then -clouddens=7 -elseif cloudspreset:find("Preset16")then -clouddens=7 -elseif cloudspreset:find("Preset17")then -clouddens=7 -elseif cloudspreset:find("Preset18")then -clouddens=7 -elseif cloudspreset:find("Preset19")then -clouddens=7 -elseif cloudspreset:find("Preset20")then -clouddens=7 -elseif cloudspreset:find("Preset21")then -clouddens=9 -elseif cloudspreset:find("Preset22")then -clouddens=9 -elseif cloudspreset:find("Preset23")then -clouddens=9 -elseif cloudspreset:find("Preset24")then -clouddens=9 -elseif cloudspreset:find("Preset25")then -clouddens=9 -elseif cloudspreset:find("Preset26")then -clouddens=9 -elseif cloudspreset:find("Preset27")then -clouddens=9 -elseif cloudspreset:find("Preset1")then -clouddens=1 -elseif cloudspreset:find("Preset2")then -clouddens=1 -elseif cloudspreset:find("Preset3")then -clouddens=4 -elseif cloudspreset:find("Preset4")then -clouddens=4 -elseif cloudspreset:find("Preset5")then -clouddens=4 -elseif cloudspreset:find("Preset6")then -clouddens=4 -elseif cloudspreset:find("Preset7")then -clouddens=4 -elseif cloudspreset:find("Preset8")then -clouddens=4 -elseif cloudspreset:find("Preset9")then -clouddens=4 -elseif cloudspreset:find("RainyPreset")then -clouddens=9 -if temperature>5 then -precepitation=1 -else -precepitation=3 -end -elseif cloudspreset:find("RainyPreset1")then -clouddens=9 -if temperature>5 then -precepitation=1 -else -precepitation=3 -end -elseif cloudspreset:find("RainyPreset2")then -clouddens=9 -if temperature>5 then -precepitation=1 -else -precepitation=3 -end -elseif cloudspreset:find("RainyPreset3")then -clouddens=9 -if temperature>5 then -precepitation=1 -else -precepitation=3 -end -end -local CLOUDBASE=string.format("%d",UTILS.MetersToFeet(cloudbase)) -local CLOUDCEIL=string.format("%d",UTILS.MetersToFeet(cloudceil)) -if self.metric then -CLOUDBASE=string.format("%d",cloudbase) -CLOUDCEIL=string.format("%d",cloudceil) -end -local CLOUDBASE1000,CLOUDBASE0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudbase)) -local CLOUDCEIL1000,CLOUDCEIL0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudceil)) -if self.metric then -CLOUDBASE1000,CLOUDBASE0100=self:_GetThousandsAndHundreds(cloudbase) -CLOUDCEIL1000,CLOUDCEIL0100=self:_GetThousandsAndHundreds(cloudceil) -end -local CloudCover={} -CloudCover=ATIS.Sound.CloudsNotAvailable -local CLOUDSsub=self.gettext:GetEntry("NOCLOUDINFO",self.locale) -if static then -if clouddens>=9 then -CloudCover=ATIS.Sound.CloudsOvercast -CLOUDSsub=self.gettext:GetEntry("OVERCAST",self.locale) -elseif clouddens>=7 then -CloudCover=ATIS.Sound.CloudsBroken -CLOUDSsub=self.gettext:GetEntry("BROKEN",self.locale) -elseif clouddens>=4 then -CloudCover=ATIS.Sound.CloudsScattered -CLOUDSsub=self.gettext:GetEntry("SCATTERED",self.locale) -elseif clouddens>=1 then -CloudCover=ATIS.Sound.CloudsFew -CLOUDSsub=self.gettext:GetEntry("FEWCLOUDS",self.locale) -else -CLOUDBASE=nil -CLOUDCEIL=nil -CloudCover=ATIS.Sound.CloudsNo -CLOUDSsub=self.gettext:GetEntry("NOCLOUDS",self.locale) -end -end -local subtitle="" -subtitle=string.format("%s",self.airbasename) -if(not self.ATISforFARPs)and self.airbasename:find("AFB")==nil and self.airbasename:find("Airport")==nil -and self.airbasename:find("Airstrip")==nil and self.airbasename:find("airfield")==nil and self.airbasename:find("AB")==nil -and self.airbasename:find("Field")==nil -then -subtitle=subtitle.." "..self.gettext:GetEntry("AIRPORT",self.locale) -end -if not self.useSRS then -self.radioqueue:NewTransmission(string.format("%s/%s.ogg",self.theatre,self.airbasename),3.0,self.soundpath,nil,nil,subtitle,self.subduration) -end -local alltext=subtitle -local information=self.gettext:GetEntry("INFORMATION",self.locale) -subtitle=string.format("%s %s",information,NATO) -local _INFORMATION=subtitle -if not self.useSRS then -self:Transmission(ATIS.Sound.Information,0.5,subtitle) -self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg",NATO),0.75,self.soundpath) -end -alltext=alltext..";\n"..subtitle -subtitle=string.format("%s Zulu",ZULU) -if not self.useSRS then -self.radioqueue:Number2Transmission(ZULU,nil,0.5) -self:Transmission(ATIS.Sound.Zulu,0.2,subtitle) -end -alltext=alltext..";\n"..subtitle -if not self.zulutimeonly then -local sunrise=self.gettext:GetEntry("SUNRISEAT",self.locale) -subtitle=string.format(sunrise,SUNRISE) -if not self.useSRS then -self:Transmission(ATIS.Sound.SunriseAt,0.5,subtitle) -self.radioqueue:Number2Transmission(SUNRISE,nil,0.2) -self:Transmission(ATIS.Sound.TimeLocal,0.2) -end -alltext=alltext..";\n"..subtitle -local sunset=self.gettext:GetEntry("SUNSETAT",self.locale) -subtitle=string.format(sunset,SUNSET) -if not self.useSRS then -self:Transmission(ATIS.Sound.SunsetAt,0.5,subtitle) -self.radioqueue:Number2Transmission(SUNSET,nil,0.5) -self:Transmission(ATIS.Sound.TimeLocal,0.2) -end -alltext=alltext..";\n"..subtitle -end -if self.useSRS then -WINDFROM=string.gsub(WINDFROM,".","%1 ") -end -if self.metric then -local windfrom=self.gettext:GetEntry("WINDFROMMS",self.locale) -subtitle=string.format(windfrom,WINDFROM,WINDSPEED) -else -local windfrom=self.gettext:GetEntry("WINDFROMKNOTS",self.locale) -subtitle=string.format(windfrom,WINDFROM,WINDSPEED) -end -if turbulence>0 then -subtitle=subtitle..", "..self.gettext:GetEntry("GUSTING",self.locale) -end -local _WIND=subtitle -if not self.useSRS then -self:Transmission(ATIS.Sound.WindFrom,1.0,subtitle) -self.radioqueue:Number2Transmission(WINDFROM) -self:Transmission(ATIS.Sound.At,0.2) -self.radioqueue:Number2Transmission(WINDSPEED) -if self.metric then -self:Transmission(ATIS.Sound.MetersPerSecond,0.2) -else -self:Transmission(ATIS.Sound.Knots,0.2) -end -if turbulence>0 then -self:Transmission(ATIS.Sound.Gusting,0.2) -end -end -alltext=alltext..";\n"..subtitle -if self.metric then -local visi=self.gettext:GetEntry("VISIKM",self.locale) -subtitle=string.format(visi,VISIBILITY) -else -local visi=self.gettext:GetEntry("VISISM",self.locale) -subtitle=string.format(visi,VISIBILITY) -end -if not self.useSRS then -self:Transmission(ATIS.Sound.Visibilty,1.0,subtitle) -self.radioqueue:Number2Transmission(VISIBILITY) -if self.metric then -self:Transmission(ATIS.Sound.Kilometers,0.2) -else -self:Transmission(ATIS.Sound.StatuteMiles,0.2) -end -end -alltext=alltext..";\n"..subtitle -subtitle="" -local wp=false -local wpsub="" -if precepitation==1 then -wp=true -wpsub=wpsub.." "..self.gettext:GetEntry("RAIN",self.locale) -elseif precepitation==2 then -if wp then -wpsub=wpsub.."," -end -wpsub=wpsub.." "..self.gettext:GetEntry("TSTORM",self.locale) -wp=true -elseif precepitation==3 then -wpsub=wpsub.." "..self.gettext:GetEntry("SNOW",self.locale) -wp=true -elseif precepitation==4 then -wpsub=wpsub.." "..self.gettext:GetEntry("SSTROM",self.locale) -wp=true -end -if fog then -if wp then -wpsub=wpsub.."," -end -wpsub=wpsub.." "..self.gettext:GetEntry("FOG",self.locale) -wp=true -end -if dust then -if wp then -wpsub=wpsub.."," -end -wpsub=wpsub.." "..self.gettext:GetEntry("DUST",self.locale) -wp=true -end -if wp then -local phenos=self.gettext:GetEntry("PHENOMENA",self.locale) -subtitle=string.format("%s: %s",phenos,wpsub) -if not self.useSRS then -self:Transmission(ATIS.Sound.WeatherPhenomena,1.0,subtitle) -if precepitation==1 then -self:Transmission(ATIS.Sound.Rain,0.5) -elseif precepitation==2 then -self:Transmission(ATIS.Sound.ThunderStorm,0.5) -elseif precepitation==3 then -self:Transmission(ATIS.Sound.Snow,0.5) -elseif precepitation==4 then -self:Transmission(ATIS.Sound.SnowStorm,0.5) -end -if fog then -self:Transmission(ATIS.Sound.Fog,0.5) -end -if dust then -self:Transmission(ATIS.Sound.Dust,0.5) -end -end -alltext=alltext..";\n"..subtitle -end -if not self.useSRS then -self:Transmission(CloudCover,1.0,CLOUDSsub) -end -if CLOUDBASE and static then -local cbase=tostring(tonumber(CLOUDBASE1000)*1000+tonumber(CLOUDBASE0100)*100) -local cceil=tostring(tonumber(CLOUDCEIL1000)*1000+tonumber(CLOUDCEIL0100)*100) -if self.metric then -local cloudbase=self.gettext:GetEntry("CLOUDBASEM",self.locale) -subtitle=string.format(cloudbase,cbase,cceil) -else -local cloudbase=self.gettext:GetEntry("CLOUDBASEFT",self.locale) -subtitle=string.format(cloudbase,cbase,cceil) -end -if not self.useSRS then -self:Transmission(ATIS.Sound.CloudBase,1.0,subtitle) -if tonumber(CLOUDBASE1000)>0 then -self.radioqueue:Number2Transmission(CLOUDBASE1000) -self:Transmission(ATIS.Sound.Thousand,0.1) -end -if tonumber(CLOUDBASE0100)>0 then -self.radioqueue:Number2Transmission(CLOUDBASE0100) -self:Transmission(ATIS.Sound.Hundred,0.1) -end -self:Transmission(ATIS.Sound.CloudCeiling,0.5) -if tonumber(CLOUDCEIL1000)>0 then -self.radioqueue:Number2Transmission(CLOUDCEIL1000) -self:Transmission(ATIS.Sound.Thousand,0.1) -end -if tonumber(CLOUDCEIL0100)>0 then -self.radioqueue:Number2Transmission(CLOUDCEIL0100) -self:Transmission(ATIS.Sound.Hundred,0.1) -end -if self.metric then -self:Transmission(ATIS.Sound.Meters,0.1) -else -self:Transmission(ATIS.Sound.Feet,0.1) -end -end -end -alltext=alltext..";\n"..subtitle -subtitle="" -local temptext=self.gettext:GetEntry("TEMPERATURE",self.locale) -if self.TDegF then -if temperature<0 then -subtitle=string.format("%s -%s °F",temptext,TEMPERATURE) -else -subtitle=string.format("%s %s °F",temptext,TEMPERATURE) -end -else -if temperature<0 then -subtitle=string.format("%s -%s °C",temptext,TEMPERATURE) -else -subtitle=string.format("%s %s °C",temptext,TEMPERATURE) -end -end -local _TEMPERATURE=subtitle -if not self.useSRS then -self:Transmission(ATIS.Sound.Temperature,1.0,subtitle) -if temperature<0 then -self:Transmission(ATIS.Sound.Minus,0.2) -end -self.radioqueue:Number2Transmission(TEMPERATURE) -if self.TDegF then -self:Transmission(ATIS.Sound.DegreesFahrenheit,0.2) -else -self:Transmission(ATIS.Sound.DegreesCelsius,0.2) -end -end -alltext=alltext..";\n"..subtitle -local dewtext=self.gettext:GetEntry("DEWPOINT",self.locale) -if self.TDegF then -if dewpoint<0 then -subtitle=string.format("%s -%s °F",dewtext,DEWPOINT) -else -subtitle=string.format("%s %s °F",dewtext,DEWPOINT) -end -else -if dewpoint<0 then -subtitle=string.format("%s -%s °C",dewtext,DEWPOINT) -else -subtitle=string.format("%s %s °C",dewtext,DEWPOINT) -end -end -local _DEWPOINT=subtitle -if not self.useSRS then -self:Transmission(ATIS.Sound.DewPoint,1.0,subtitle) -if dewpoint<0 then -self:Transmission(ATIS.Sound.Minus,0.2) -end -self.radioqueue:Number2Transmission(DEWPOINT) -if self.TDegF then -self:Transmission(ATIS.Sound.DegreesFahrenheit,0.2) -else -self:Transmission(ATIS.Sound.DegreesCelsius,0.2) -end -end -alltext=alltext..";\n"..subtitle -local altim=self.gettext:GetEntry("ALTIMETER",self.locale) -if self.PmmHg then -if self.qnhonly then -subtitle=string.format("%s %s.%s mmHg",altim,QNH[1],QNH[2]) -else -subtitle=string.format("%s: QNH %s.%s, QFE %s.%s mmHg",altim,QNH[1],QNH[2],QFE[1],QFE[2]) -end -else -if self.metric then -if self.qnhonly then -subtitle=string.format("%s %s.%s hPa",altim,QNH[1],QNH[2]) -else -subtitle=string.format("%s: QNH %s.%s, QFE %s.%s hPa",altim,QNH[1],QNH[2],QFE[1],QFE[2]) -end -else -if self.qnhonly then -subtitle=string.format("%s %s.%s inHg",altim,QNH[1],QNH[2]) -else -subtitle=string.format("%s: QNH %s.%s, QFE %s.%s inHg",altim,QNH[1],QNH[2],QFE[1],QFE[2]) -end -end -end -if self.ReportmBar and not self.metric then -if self.qnhonly then -subtitle=string.format("%s;\n%s %d hPa",subtitle,altim,mBarqnh) -else -subtitle=string.format("%s;\n%s: QNH %d, QFE %d hPa",subtitle,altim,mBarqnh,mBarqfe) -end -end -local _ALTIMETER=subtitle -if not self.useSRS then -self:Transmission(ATIS.Sound.Altimeter,1.0,subtitle) -if not self.qnhonly then -self:Transmission(ATIS.Sound.QNH,0.5) -end -self.radioqueue:Number2Transmission(QNH[1]) -if ATIS.ICAOPhraseology[UTILS.GetDCSMap()]then -self:Transmission(ATIS.Sound.Decimal,0.2) -end -self.radioqueue:Number2Transmission(QNH[2]) -if not self.qnhonly then -self:Transmission(ATIS.Sound.QFE,0.75) -self.radioqueue:Number2Transmission(QFE[1]) -if ATIS.ICAOPhraseology[UTILS.GetDCSMap()]then -self:Transmission(ATIS.Sound.Decimal,0.2) -end -self.radioqueue:Number2Transmission(QFE[2]) -end -if self.PmmHg then -self:Transmission(ATIS.Sound.MillimetersOfMercury,0.1) -else -if self.metric then -self:Transmission(ATIS.Sound.HectoPascal,0.1) -else -self:Transmission(ATIS.Sound.InchesOfMercury,0.1) -end -end -end -alltext=alltext..";\n"..subtitle -local _RUNACT -if not self.ATISforFARPs then -local subtitle="" -if runwayLanding then -local actrun=self.gettext:GetEntry("ACTIVELANDING",self.locale) -subtitle=string.format("%s %s",actrun,runwayLanding) -if rwyLandingLeft==true then -subtitle=subtitle.." "..self.gettext:GetEntry("LEFT",self.locale) -elseif rwyLandingLeft==false then -subtitle=subtitle.." "..self.gettext:GetEntry("RIGHT",self.locale) -end -alltext=alltext..";\n"..subtitle -end -if runwayTakeoff then -local actrun=self.gettext:GetEntry("ACTIVERUN",self.locale) -subtitle=string.format("%s %s",actrun,runwayTakeoff) -if rwyTakeoffLeft==true then -subtitle=subtitle.." "..self.gettext:GetEntry("LEFT",self.locale) -elseif rwyTakeoffLeft==false then -subtitle=subtitle.." "..self.gettext:GetEntry("RIGHT",self.locale) -end -end -_RUNACT=subtitle -if not self.useSRS then -self:Transmission(ATIS.Sound.ActiveRunway,1.0,subtitle) -self.radioqueue:Number2Transmission(runwayLanding) -if rwyLandingLeft==true then -self:Transmission(ATIS.Sound.Left,0.2) -elseif rwyLandingLeft==false then -self:Transmission(ATIS.Sound.Right,0.2) -end -end -alltext=alltext..";\n"..subtitle -if self.rwylength then -local runact=self.airbase:GetActiveRunway(self.runwaym2t) -local length=runact.length -if not self.metric then -length=UTILS.MetersToFeet(length) -end -local L1000,L0100=self:_GetThousandsAndHundreds(length) -local rwyl=self.gettext:GetEntry("RWYLENGTH",self.locale) -local meters=self.gettext:GetEntry("METERS",self.locale) -local feet=self.gettext:GetEntry("FEET",self.locale) -local subtitle=string.format("%s %d",rwyl,length) -if self.metric then -subtitle=subtitle.." "..meters -else -subtitle=subtitle.." "..feet -end -if not self.useSRS then -self:Transmission(ATIS.Sound.RunwayLength,1.0,subtitle) -if tonumber(L1000)>0 then -self.radioqueue:Number2Transmission(L1000) -self:Transmission(ATIS.Sound.Thousand,0.1) -end -if tonumber(L0100)>0 then -self.radioqueue:Number2Transmission(L0100) -self:Transmission(ATIS.Sound.Hundred,0.1) -end -if self.metric then -self:Transmission(ATIS.Sound.Meters,0.1) -else -self:Transmission(ATIS.Sound.Feet,0.1) -end -end -alltext=alltext..";\n"..subtitle -end -end -if self.elevation then -local elev=self.gettext:GetEntry("ELEVATION",self.locale) -local meters=self.gettext:GetEntry("METERS",self.locale) -local feet=self.gettext:GetEntry("FEET",self.locale) -local elevation=self.airbase:GetHeight() -if not self.metric then -elevation=UTILS.MetersToFeet(elevation) -end -local L1000,L0100=self:_GetThousandsAndHundreds(elevation) -local subtitle=string.format("%s %d",elev,elevation) -if self.metric then -subtitle=subtitle.." "..meters -else -subtitle=subtitle.." "..feet -end -if not self.useSRS then -self:Transmission(ATIS.Sound.Elevation,1.0,subtitle) -if tonumber(L1000)>0 then -self.radioqueue:Number2Transmission(L1000) -self:Transmission(ATIS.Sound.Thousand,0.1) -end -if tonumber(L0100)>0 then -self.radioqueue:Number2Transmission(L0100) -self:Transmission(ATIS.Sound.Hundred,0.1) -end -if self.metric then -self:Transmission(ATIS.Sound.Meters,0.1) -else -self:Transmission(ATIS.Sound.Feet,0.1) -end -end -alltext=alltext..";\n"..subtitle -end -if self.towerfrequency then -local freqs="" -for i,freq in pairs(self.towerfrequency)do -freqs=freqs..string.format("%.3f MHz",freq) -if i<#self.towerfrequency then -freqs=freqs..", " -end -end -local twrfrq=self.gettext:GetEntry("TOWERFREQ",self.locale) -subtitle=string.format("%s %s",twrfrq,freqs) -if not self.useSRS then -self:Transmission(ATIS.Sound.TowerFrequency,1.0,subtitle) -for _,freq in pairs(self.towerfrequency)do -local f=string.format("%.3f",freq) -f=UTILS.Split(f,".") -self.radioqueue:Number2Transmission(f[1],nil,0.5) -if tonumber(f[2])>0 then -self:Transmission(ATIS.Sound.Decimal,0.2) -self.radioqueue:Number2Transmission(f[2]) -end -self:Transmission(ATIS.Sound.MegaHertz,0.2) -end -end -alltext=alltext..";\n"..subtitle -end -local ils=self:GetNavPoint(self.ils,runwayLanding,rwyLandingLeft) -if ils then -local ilstxt=self.gettext:GetEntry("ILSFREQ",self.locale) -subtitle=string.format("%s %.2f MHz",ilstxt,ils.frequency) -if not self.useSRS then -self:Transmission(ATIS.Sound.ILSFrequency,1.0,subtitle) -local f=string.format("%.2f",ils.frequency) -f=UTILS.Split(f,".") -self.radioqueue:Number2Transmission(f[1],nil,0.5) -if tonumber(f[2])>0 then -self:Transmission(ATIS.Sound.Decimal,0.2) -self.radioqueue:Number2Transmission(f[2]) -end -self:Transmission(ATIS.Sound.MegaHertz,0.2) -end -alltext=alltext..";\n"..subtitle -end -local ndb=self:GetNavPoint(self.ndbouter,runwayLanding,rwyLandingLeft) -if ndb then -local ndbtxt=self.gettext:GetEntry("OUTERNDB",self.locale) -subtitle=string.format("%s %.2f MHz",ndbtxt,ndb.frequency) -if not self.useSRS then -self:Transmission(ATIS.Sound.OuterNDBFrequency,1.0,subtitle) -local f=string.format("%.2f",ndb.frequency) -f=UTILS.Split(f,".") -self.radioqueue:Number2Transmission(f[1],nil,0.5) -if tonumber(f[2])>0 then -self:Transmission(ATIS.Sound.Decimal,0.2) -self.radioqueue:Number2Transmission(f[2]) -end -self:Transmission(ATIS.Sound.MegaHertz,0.2) -end -alltext=alltext..";\n"..subtitle -end -local ndb=self:GetNavPoint(self.ndbinner,runwayLanding,rwyLandingLeft) -if ndb then -local ndbtxt=self.gettext:GetEntry("INNERNDB",self.locale) -subtitle=string.format("%s %.2f MHz",ndbtxt,ndb.frequency) -if not self.useSRS then -self:Transmission(ATIS.Sound.InnerNDBFrequency,1.0,subtitle) -local f=string.format("%.2f",ndb.frequency) -f=UTILS.Split(f,".") -self.radioqueue:Number2Transmission(f[1],nil,0.5) -if tonumber(f[2])>0 then -self:Transmission(ATIS.Sound.Decimal,0.2) -self.radioqueue:Number2Transmission(f[2]) -end -self:Transmission(ATIS.Sound.MegaHertz,0.2) -end -alltext=alltext..";\n"..subtitle -end -if self.vor then -local vortxt=self.gettext:GetEntry("VORFREQ",self.locale) -local vorttstxt=self.gettext:GetEntry("VORFREQTTS",self.locale) -subtitle=string.format("%s %.2f MHz",vortxt,self.vor) -if self.useSRS then -subtitle=string.format("%s %.2f MHz",vorttstxt,self.vor) -end -if not self.useSRS then -self:Transmission(ATIS.Sound.VORFrequency,1.0,subtitle) -local f=string.format("%.2f",self.vor) -f=UTILS.Split(f,".") -self.radioqueue:Number2Transmission(f[1],nil,0.5) -if tonumber(f[2])>0 then -self:Transmission(ATIS.Sound.Decimal,0.2) -self.radioqueue:Number2Transmission(f[2]) -end -self:Transmission(ATIS.Sound.MegaHertz,0.2) -end -alltext=alltext..";\n"..subtitle -end -if self.tacan then -local tactxt=self.gettext:GetEntry("TACANCH",self.locale) -subtitle=string.format(tactxt,self.tacan) -if not self.useSRS then -self:Transmission(ATIS.Sound.TACANChannel,1.0,subtitle) -self.radioqueue:Number2Transmission(tostring(self.tacan),nil,0.2) -self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg",0.75,self.soundpath,nil,0.2) -end -alltext=alltext..";\n"..subtitle -end -if self.rsbn then -local rsbntxt=self.gettext:GetEntry("RSBNCH",self.locale) -subtitle=string.format("%s %d",rsbntxt,self.rsbn) -if not self.useSRS then -self:Transmission(ATIS.Sound.RSBNChannel,1.0,subtitle) -self.radioqueue:Number2Transmission(tostring(self.rsbn),nil,0.2) -end -alltext=alltext..";\n"..subtitle -end -local ndb=self:GetNavPoint(self.prmg,runwayLanding,rwyLandingLeft) -if ndb then -local prmtxt=self.gettext:GetEntry("PRMGCH",self.locale) -subtitle=string.format("%s %d",prmtxt,ndb.frequency) -if not self.useSRS then -self:Transmission(ATIS.Sound.PRMGChannel,1.0,subtitle) -self.radioqueue:Number2Transmission(tostring(ndb.frequency),nil,0.5) -end -alltext=alltext..";\n"..subtitle -end -if self.useSRS and self.AdditionalInformation then -alltext=alltext..";\n"..self.AdditionalInformation -end -local advtxt=self.gettext:GetEntry("ADVISE",self.locale) -subtitle=string.format("%s %s",advtxt,NATO) -if not self.useSRS then -self:Transmission(ATIS.Sound.AdviceOnInitial,0.5,subtitle) -self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg",NATO),0.75,self.soundpath) -end -alltext=alltext..";\n"..subtitle -self:Report(alltext) -if self.usemarker then -self:UpdateMarker(_INFORMATION,_RUNACT,_WIND,_ALTIMETER,_TEMPERATURE) -end -end -function ATIS:onafterReport(From,Event,To,Text) -self:T({From,Event,To}) -self:T(self.lid..string.format("Report:\n%s",Text)) -if self.useSRS and self.msrs then -local text=string.gsub(Text,"[\r\n]","") -local statute=self.gettext:GetEntry("STATUTE",self.locale) -local degc=self.gettext:GetEntry("DEGREES",self.locale) -local degf=self.gettext:GetEntry("FAHRENHEIT",self.locale) -local inhg=self.gettext:GetEntry("INCHHG",self.locale) -local mmhg=self.gettext:GetEntry("MMHG",self.locale) -local hpa=self.gettext:GetEntry("HECTO",self.locale) -local emes=self.gettext:GetEntry("METERSPER",self.locale) -local tacan=self.gettext:GetEntry("TACAN",self.locale) -local farp=self.gettext:GetEntry("FARP",self.locale) -local text=string.gsub(text,"SM",statute) -text=string.gsub(text,"°C",degc) -text=string.gsub(text,"°F",degf) -text=string.gsub(text,"inHg",inhg) -text=string.gsub(text,"mmHg",mmhg) -text=string.gsub(text,"hPa",hpa) -text=string.gsub(text,"m/s",emes) -text=string.gsub(text,"TACAN",tacan) -text=string.gsub(text,"FARP",farp) -local delimiter=self.gettext:GetEntry("DELIMITER",self.locale) -if string.lower(self.locale)~="en"then -text=string.gsub(text,"(%d+)(%.)(%d+)","%1 "..delimiter.." %3") -end -local text=string.gsub(text,";"," . ") -self:T("SRS TTS: "..text) -local duration=STTS.getSpeechTime(text,0.95) -self.msrsQ:NewTransmission(text,duration,self.msrs,nil,2) -self.SRSText=text -end -end -function ATIS:OnEventBaseCaptured(EventData) -if EventData and EventData.Place then -local airbase=EventData.Place -if EventData.PlaceName==self.airbasename then -local NewCoalitionAirbase=airbase:GetCoalition() -if self.useSRS and self.msrs and self.msrs.coalition~=NewCoalitionAirbase then -self.msrs:SetCoalition(NewCoalitionAirbase) -end -end -end -end -function ATIS:UpdateMarker(information,runact,wind,altimeter,temperature) -if self.markerid then -self.airbase:GetCoordinate():RemoveMark(self.markerid) -end -local text="" -if type(self.frequency)=="table"then -local frequency=table.concat(self.frequency,"/") -local modulation=self.modulation -if type(modulation)=="table"then -modulation=table.concat(self.modulation,"/") -end -text=string.format("ATIS on %s %s, %s:\n",tostring(frequency),tostring(modulation),tostring(information)) -else -text=string.format("ATIS on %.3f %s, %s:\n",self.frequency,UTILS.GetModulationName(self.modulation),tostring(information)) -end -text=text..string.format("%s\n",tostring(runact)) -text=text..string.format("%s\n",tostring(wind)) -text=text..string.format("%s\n",tostring(altimeter)) -text=text..string.format("%s",tostring(temperature)) -self.markerid=self.airbase:GetCoordinate():MarkToAll(text,true) -return self.markerid -end -function ATIS:GetActiveRunway(Takeoff) -local runway=nil -if Takeoff then -runway=self.airbase:GetActiveRunwayTakeoff() -else -runway=self.airbase:GetActiveRunwayLanding() -end -if runway then -return runway.name,runway.isLeft -else -return nil,nil -end -end -function ATIS:GetMagneticRunway(windfrom) -local diffmin=nil -local runway=nil -for _,heading in pairs(self.runwaymag)do -local hdg=self:GetRunwayWithoutLR(heading) -local diff=UTILS.HdgDiff(windfrom,tonumber(hdg)*10) -if diffmin==nil or diff0 then -for _,_cargo in pairs(crates)do -local cgotype=_cargo:GetType() -if _cargo:WasDropped()and cgotype~=CTLD_CARGO.Enum.STATIC then -local ok=false -local chalk=_cargo:GetMark() -if chalk==nil then -ok=true -else -local tag=chalk.tag or"none" -local timestamp=chalk.timestamp or 0 -local gone=timer.getAbsTime()-timestamp -if gone>=self.marktimer then -ok=true -_cargo:WipeMark() -end -end -if ok then -local chalk={} -chalk.tag="Engineers" -chalk.timestamp=timer.getAbsTime() -_cargo:AddMark(chalk) -ind=ind+1 -table.insert(ctable,ind,_cargo) -end -end -end -end -if ind>0 then -local crate=ctable[1] -local static=crate:GetPositionable() -local crate_pos=static:GetCoordinate() -local gpos=group:GetCoordinate() -local distance=self:_GetDistance(gpos,crate_pos) -self:T(string.format("%s Distance to crate: %d",self.lid,distance)) -if distance>30 and distance~=-1 and self:IsStatus("Searching")then -group:RouteGroundTo(crate_pos,15,"Line abreast",1) -self.currwpt=crate_pos -self:Move() -elseif distance<=30 and distance~=-1 then -self:Arrive() -end -else -self:T(self.lid.."No crates in reach!") -end -return self -end -function CTLD_ENGINEERING:Move() -self:T(self.lid.."Move") -self:SetStatus("Moving") -local group=self.Group -local tgtpos=self.currwpt -local gpos=group:GetCoordinate() -local distance=self:_GetDistance(gpos,tgtpos) -self:T(string.format("%s Distance remaining: %d",self.lid,distance)) -if distance<=30 and distance~=-1 then -self:Arrive() -end -return self -end -function CTLD_ENGINEERING:Arrive() -self:T(self.lid.."Arrive") -self:SetStatus("Arrived") -self.currwpt=nil -local Grp=self.Group -Grp:RouteStop() -return self -end -function CTLD_ENGINEERING:_GetDistance(_point1,_point2) -self:T(self.lid.." _GetDistance") -if _point1 and _point2 then -local distance1=_point1:Get2DDistance(_point2) -local distance2=_point1:DistanceFromPointVec2(_point2) -if distance1 and type(distance1)=="number"then -return distance1 -elseif distance2 and type(distance2)=="number"then -return distance2 -else -self:E("*****Cannot calculate distance!") -self:E({_point1,_point2}) -return-1 -end -else -self:E("******Cannot calculate distance!") -self:E({_point1,_point2}) -return-1 -end -end -end -do -CTLD={ -ClassName="CTLD", -verbose=0, -lid="", -coalition=1, -coalitiontxt="blue", -PilotGroups={}, -CtldUnits={}, -FreeVHFFrequencies={}, -FreeUHFFrequencies={}, -FreeFMFrequencies={}, -CargoCounter=0, -Cargo_Troops={}, -Cargo_Crates={}, -Loaded_Cargo={}, -Spawned_Crates={}, -Spawned_Cargo={}, -CrateDistance=35, -PackDistance=35, -debug=false, -wpZones={}, -dropOffZones={}, -pickupZones={}, -} -CTLD.RadioModulation={ -AM=0, -FM=1, -} -CTLD.CargoZoneType={ -LOAD="load", -DROP="drop", -MOVE="move", -SHIP="ship", -BEACON="beacon", -} -CTLD.UnitTypeCapabilities={ -["SA342Mistral"]={type="SA342Mistral",crates=false,troops=true,cratelimit=0,trooplimit=4,length=12,cargoweightlimit=400}, -["SA342L"]={type="SA342L",crates=false,troops=true,cratelimit=0,trooplimit=2,length=12,cargoweightlimit=400}, -["SA342M"]={type="SA342M",crates=false,troops=true,cratelimit=0,trooplimit=4,length=12,cargoweightlimit=400}, -["SA342Minigun"]={type="SA342Minigun",crates=false,troops=true,cratelimit=0,trooplimit=2,length=12,cargoweightlimit=400}, -["UH-1H"]={type="UH-1H",crates=true,troops=true,cratelimit=1,trooplimit=8,length=15,cargoweightlimit=700}, -["Mi-8MTV2"]={type="Mi-8MTV2",crates=true,troops=true,cratelimit=2,trooplimit=12,length=15,cargoweightlimit=3000}, -["Mi-8MT"]={type="Mi-8MT",crates=true,troops=true,cratelimit=2,trooplimit=12,length=15,cargoweightlimit=3000}, -["Ka-50"]={type="Ka-50",crates=false,troops=false,cratelimit=0,trooplimit=0,length=15,cargoweightlimit=0}, -["Ka-50_3"]={type="Ka-50_3",crates=false,troops=false,cratelimit=0,trooplimit=0,length=15,cargoweightlimit=0}, -["Mi-24P"]={type="Mi-24P",crates=true,troops=true,cratelimit=2,trooplimit=8,length=18,cargoweightlimit=700}, -["Mi-24V"]={type="Mi-24V",crates=true,troops=true,cratelimit=2,trooplimit=8,length=18,cargoweightlimit=700}, -["Hercules"]={type="Hercules",crates=true,troops=true,cratelimit=7,trooplimit=64,length=25,cargoweightlimit=19000}, -["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, -["AH-64D_BLK_II"]={type="AH-64D_BLK_II",crates=false,troops=true,cratelimit=0,trooplimit=2,length=17,cargoweightlimit=200}, -["Bronco-OV-10A"]={type="Bronco-OV-10A",crates=false,troops=true,cratelimit=0,trooplimit=5,length=13,cargoweightlimit=1450}, -} -CTLD.version="1.0.45" -function CTLD:New(Coalition,Prefixes,Alias) -local self=BASE:Inherit(self,FSM:New()) -BASE:T({Coalition,Prefixes,Alias}) -if Coalition and type(Coalition)=="string"then -if Coalition=="blue"then -self.coalition=coalition.side.BLUE -self.coalitiontxt=Coalition -elseif Coalition=="red"then -self.coalition=coalition.side.RED -self.coalitiontxt=Coalition -elseif Coalition=="neutral"then -self.coalition=coalition.side.NEUTRAL -self.coalitiontxt=Coalition -else -self:E("ERROR: Unknown coalition in CTLD!") -end -else -self.coalition=Coalition -self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition)) -end -if Alias then -self.alias=tostring(Alias) -else -self.alias="UNHCR" -if self.coalition then -if self.coalition==coalition.side.RED then -self.alias="Red CTLD" -elseif self.coalition==coalition.side.BLUE then -self.alias="Blue CTLD" -end -end -end -self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","Status","*") -self:AddTransition("*","TroopsPickedUp","*") -self:AddTransition("*","TroopsExtracted","*") -self:AddTransition("*","CratesPickedUp","*") -self:AddTransition("*","TroopsDeployed","*") -self:AddTransition("*","TroopsRTB","*") -self:AddTransition("*","CratesDropped","*") -self:AddTransition("*","CratesBuild","*") -self:AddTransition("*","CratesRepaired","*") -self:AddTransition("*","CratesBuildStarted","*") -self:AddTransition("*","CratesRepairStarted","*") -self:AddTransition("*","Load","*") -self:AddTransition("*","Save","*") -self:AddTransition("*","Stop","Stopped") -self.PilotGroups={} -self.CtldUnits={} -self.FreeVHFFrequencies={} -self.FreeUHFFrequencies={} -self.FreeFMFrequencies={} -self.UsedVHFFrequencies={} -self.UsedUHFFrequencies={} -self.UsedFMFrequencies={} -self.RadioSound="beacon.ogg" -self.RadioSoundFC3="beacon.ogg" -self.RadioPath="l10n/DEFAULT/" -self.pickupZones={} -self.dropOffZones={} -self.wpZones={} -self.shipZones={} -self.droppedBeacons={} -self.droppedbeaconref={} -self.droppedbeacontimeout=600 -self.useprecisecoordloads=true -self.Cargo_Crates={} -self.Cargo_Troops={} -self.Cargo_Statics={} -self.Loaded_Cargo={} -self.Spawned_Crates={} -self.Spawned_Cargo={} -self.MenusDone={} -self.DroppedTroops={} -self.DroppedCrates={} -self.CargoCounter=0 -self.CrateCounter=0 -self.TroopCounter=0 -self.Engineers=0 -self.EngineersInField={} -self.EngineerSearch=2000 -self.nobuildmenu=false -self.CrateDistance=35 -self.PackDistance=35 -self.ExtractFactor=3.33 -self.prefixes=Prefixes or{"Cargoheli"} -self.useprefix=true -self.maximumHoverHeight=15 -self.minimumHoverHeight=4 -self.forcehoverload=true -self.hoverautoloading=true -self.dropcratesanywhere=false -self.dropAsCargoCrate=false -self.smokedistance=2000 -self.movetroopstowpzone=true -self.movetroopsdistance=5000 -self.troopdropzoneradius=100 -self.enableHercules=false -self.HercMinAngels=165 -self.HercMaxAngels=2000 -self.HercMaxSpeed=77 -self.suppressmessages=false -self.repairtime=300 -self.buildtime=300 -self.placeCratesAhead=false -self.cratecountry=country.id.GERMANY -self.pilotmustopendoors=false -if self.coalition==coalition.side.RED then -self.cratecountry=country.id.RUSSIA -end -self.enableLoadSave=false -self.filepath=nil -self.saveinterval=600 -self.eventoninject=true -self.usesubcats=false -self.subcats={} -self.subcatsTroop={} -self.nobuildinloadzones=true -self.movecratesbeforebuild=true -self.surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} -local AliaS=string.gsub(self.alias," ","_") -self.filename=string.format("CTLD_%s_Persist.csv",AliaS) -self.allowcratepickupagain=true -self.enableslingload=false -self.basetype="container_cargo" -self.SmokeColor=SMOKECOLOR.Red -self.FlareColor=FLARECOLOR.Red -for i=1,100 do -math.random() -end -self:_GenerateVHFrequencies() -self:_GenerateUHFrequencies() -self:_GenerateFMFrequencies() -return self -end -function CTLD:_GetUnitCapabilities(Unit) -self:T(self.lid.." _GetUnitCapabilities") -local _unit=Unit -local unittype=_unit:GetTypeName() -local capabilities=self.UnitTypeCapabilities[unittype] -if not capabilities or capabilities=={}then -capabilities={} -capabilities.troops=false -capabilities.crates=false -capabilities.cratelimit=0 -capabilities.trooplimit=0 -capabilities.type="generic" -capabilities.length=20 -capabilities.cargoweightlimit=0 -end -return capabilities -end -function CTLD:_GenerateUHFrequencies() -self:T(self.lid.." _GenerateUHFrequencies") -self.FreeUHFFrequencies={} -self.FreeUHFFrequencies=UTILS.GenerateUHFrequencies(243,320) -return self -end -function CTLD:_GenerateFMFrequencies() -self:T(self.lid.." _GenerateFMrequencies") -self.FreeFMFrequencies={} -self.FreeFMFrequencies=UTILS.GenerateFMFrequencies() -return self -end -function CTLD:_GenerateVHFrequencies() -self:T(self.lid.." _GenerateVHFrequencies") -self.FreeVHFFrequencies={} -self.UsedVHFFrequencies={} -self.FreeVHFFrequencies=UTILS.GenerateVHFrequencies() -return self -end -function CTLD:SetTroopDropZoneRadius(Radius) -self:T(self.lid.." SetTroopDropZoneRadius") -local tradius=Radius or 100 -if tradius<25 then tradius=25 end -self.troopdropzoneradius=tradius -return self -end -function CTLD:AddPlayerTask(PlayerTask) -self:T(self.lid.." AddPlayerTask") -if not self.PlayerTaskQueue then -self.PlayerTaskQueue=FIFO:New() -end -self.PlayerTaskQueue:Push(PlayerTask,PlayerTask.PlayerTaskNr) -return self -end -function CTLD:_EventHandler(EventData) -self:T(string.format("%s Event = %d",self.lid,EventData.id)) -local event=EventData -if event.id==EVENTS.PlayerEnterAircraft or event.id==EVENTS.PlayerEnterUnit then -local _coalition=event.IniCoalition -if _coalition~=self.coalition then -return -end -local unitname=event.IniUnitName or"none" -self.MenusDone[unitname]=nil -local _unit=event.IniUnit -local _group=event.IniGroup -if _unit:IsHelicopter()or _group:IsHelicopter()then -local unitname=event.IniUnitName or"none" -self.Loaded_Cargo[unitname]=nil -self:_RefreshF10Menus() -end -if self:IsHercules(_unit)and self.enableHercules then -local unitname=event.IniUnitName or"none" -self.Loaded_Cargo[unitname]=nil -self:_RefreshF10Menus() -end -return -elseif event.id==EVENTS.PlayerLeaveUnit then -local unitname=event.IniUnitName or"none" -self.CtldUnits[unitname]=nil -self.Loaded_Cargo[unitname]=nil -self.MenusDone[unitname]=nil -end -return self -end -function CTLD:_SendMessage(Text,Time,Clearscreen,Group) -self:T(self.lid.." _SendMessage") -if not self.suppressmessages then -local m=MESSAGE:New(Text,Time,"CTLD",Clearscreen):ToGroup(Group) -end -return self -end -function CTLD:_FindTroopsCargoObject(Name) -self:T(self.lid.." _FindTroopsCargoObject") -local cargo=nil -for _,_cargo in pairs(self.Cargo_Troops)do -local cargo=_cargo -if cargo.Name==Name then -return cargo -end -end -return nil -end -function CTLD:_FindCratesCargoObject(Name) -self:T(self.lid.." _FindCratesCargoObject") -local cargo=nil -for _,_cargo in pairs(self.Cargo_Crates)do -local cargo=_cargo -if cargo.Name==Name then -return cargo -end -end -return nil -end -function CTLD:PreloadTroops(Unit,Troopname) -self:T(self.lid.." PreloadTroops") -local name=Troopname or"Unknown" -if Unit and Unit:IsAlive()then -local cargo=self:_FindTroopsCargoObject(name) -local group=Unit:GetGroup() -if cargo then -self:_LoadTroops(group,Unit,cargo,true) -else -self:E(self.lid.." Troops preload - Cargo Object "..name.." not found!") -end -end -return self -end -function CTLD:_PreloadCrates(Group,Unit,Cargo,NumberOfCrates) -local group=Group -local unit=Unit -local unitname=unit:GetName() -local unittype=unit:GetTypeName() -local capabilities=self:_GetUnitCapabilities(Unit) -local cancrates=capabilities.crates -local cratelimit=capabilities.cratelimit -if not cancrates then -self:_SendMessage("Sorry this chopper cannot carry crates!",10,false,Group) -return self -else -local numberonboard=0 -local massonboard=0 -local loaded={} -if self.Loaded_Cargo[unitname]then -loaded=self.Loaded_Cargo[unitname] -numberonboard=loaded.Cratesloaded or 0 -massonboard=self:_GetUnitCargoMass(Unit) -else -loaded={} -loaded.Troopsloaded=0 -loaded.Cratesloaded=0 -loaded.Cargo={} -end -local crate=Cargo -local numbercrates=NumberOfCrates or crate:GetCratesNeeded() -for i=1,numbercrates do -loaded.Cratesloaded=loaded.Cratesloaded+1 -crate:SetHasMoved(true) -crate:SetWasDropped(false) -table.insert(loaded.Cargo,crate) -crate.Positionable=nil -self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,false,Group) -self.Loaded_Cargo[unitname]=loaded -self:_UpdateUnitCargoMass(Unit) -end -end -return self -end -function CTLD:PreloadCrates(Unit,Cratesname,NumberOfCrates) -self:T(self.lid.." PreloadCrates") -local name=Cratesname or"Unknown" -if Unit and Unit:IsAlive()then -local cargo=self:_FindCratesCargoObject(name) -local group=Unit:GetGroup() -if cargo then -self:_PreloadCrates(group,Unit,cargo,NumberOfCrates) -else -self:E(self.lid.." Crates preload - Cargo Object "..name.." not found!") -end -end -return self -end -function CTLD:_LoadTroops(Group,Unit,Cargotype,Inject) -self:T(self.lid.." _LoadTroops") -local instock=Cargotype:GetStock() -local cgoname=Cargotype:GetName() -local cgotype=Cargotype:GetType() -local cgonetmass=Cargotype:GetNetMass() -local maxloadable=self:_GetMaxLoadableMass(Unit) -if type(instock)=="number"and tonumber(instock)<=0 and tonumber(instock)~=-1 and not Inject then -self:_SendMessage(string.format("Sorry, all %s are gone!",cgoname),10,false,Group) -return self -end -local grounded=not self:IsUnitInAir(Unit) -local hoverload=self:CanHoverLoad(Unit) -local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) -if not inzone then -inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) -end -if not Inject then -if not inzone then -self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) -if not self.debug then return self end -elseif not grounded and not hoverload then -self:_SendMessage("You need to land or hover in position to load!",10,false,Group) -if not self.debug then return self end -elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then -self:_SendMessage("You need to open the door(s) to load troops!",10,false,Group) -if not self.debug then return self end -end -end -local group=Group -local unit=Unit -local unitname=unit:GetName() -local cargotype=Cargotype -local cratename=cargotype:GetName() -local unittype=unit:GetTypeName() -local capabilities=self:_GetUnitCapabilities(Unit) -local cantroops=capabilities.troops -local trooplimit=capabilities.trooplimit -local troopsize=cargotype:GetCratesNeeded() -local numberonboard=0 -local loaded={} -if self.Loaded_Cargo[unitname]then -loaded=self.Loaded_Cargo[unitname] -numberonboard=loaded.Troopsloaded or 0 -else -loaded={} -loaded.Troopsloaded=0 -loaded.Cratesloaded=0 -loaded.Cargo={} -end -if troopsize+numberonboard>trooplimit then -self:_SendMessage("Sorry, we\'re crammed already!",10,false,Group) -return -elseif maxloadableself.EngineerSearch then -self:_SendMessage("No unit close enough to repair!",10,false,Group) -return nil,nil -end -local groupname=nearestGroup:GetName() -local function matchstring(String,Table) -local match=false -String=string.gsub(String,"-"," ") -if type(Table)=="table"then -for _,_name in pairs(Table)do -_name=string.gsub(_name,"-"," ") -if string.find(String,_name)then -match=true -break -end -end -else -if type(String)=="string"then -Table=string.gsub(Table,"-"," ") -if string.find(String,Table)then match=true end -end -end -return match -end -local Cargotype=nil -for k,v in pairs(self.Cargo_Crates)do -if matchstring(groupname,v.Templates)and matchstring(groupname,Repairtype)then -Cargotype=v -break -end -end -if Cargotype==nil then -return nil,nil -else -return nearestGroup,Cargotype -end -end -function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering) -self:T(self.lid.." _RepairObjectFromCrates") -local build=Build -local Repairtype=build.Template -local NearestGroup,CargoType=self:_FindRepairNearby(Group,Unit,Repairtype) -if NearestGroup~=nil then -if self.repairtime<2 then self.repairtime=30 end -if not Engineering then -self:_SendMessage(string.format("Repair started using %s taking %d secs",build.Name,self.repairtime),10,false,Group) -end -local name=CargoType:GetName() -local required=CargoType:GetCratesNeeded() -local template=CargoType:GetTemplates() -local ctype=CargoType:GetType() -local object={} -object.Name=CargoType:GetName() -object.Required=required -object.Found=required -object.Template=template -object.CanBuild=true -object.Type=ctype -self:_CleanUpCrates(Crates,Build,Number) -local desttimer=TIMER:New(function()NearestGroup:Destroy(false)end,self) -desttimer:Start(self.repairtime-1) -local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,object,true,NearestGroup:GetCoordinate()) -buildtimer:Start(self.repairtime) -self:__CratesRepairStarted(1,Group,Unit) -else -if not Engineering then -self:_SendMessage("Can't repair this unit with "..build.Name,10,false,Group) -else -self:T("Can't repair this unit with "..build.Name) -end -end -return self -end -function CTLD:_ExtractTroops(Group,Unit) -self:T(self.lid.." _ExtractTroops") -local grounded=not self:IsUnitInAir(Unit) -local hoverload=self:CanHoverLoad(Unit) -if not grounded and not hoverload then -self:_SendMessage("You need to land or hover in position to load!",10,false,Group) -if not self.debug then return self end -end -if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then -self:_SendMessage("You need to open the door(s) to extract troops!",10,false,Group) -if not self.debug then return self end -end -local unit=Unit -local unitname=unit:GetName() -local unittype=unit:GetTypeName() -local capabilities=self:_GetUnitCapabilities(Unit) -local cantroops=capabilities.troops -local trooplimit=capabilities.trooplimit -local unitcoord=unit:GetCoordinate() -local nearestGroup=nil -local nearestGroupIndex=-1 -local nearestDistance=10000000 -local nearestList={} -local distancekeys={} -local extractdistance=self.CrateDistance*self.ExtractFactor -for k,v in pairs(self.DroppedTroops)do -local distance=self:_GetDistance(v:GetCoordinate(),unitcoord) -if distance<=extractdistance and distance~=-1 then -nearestGroup=v -nearestGroupIndex=k -nearestDistance=distance -table.insert(nearestList,math.floor(distance),v) -distancekeys[#distancekeys+1]=math.floor(distance) -end -end -if nearestGroup==nil or nearestDistance>extractdistance then -self:_SendMessage("No units close enough to extract!",10,false,Group) -return self -end -table.sort(distancekeys) -local secondarygroups={} -for i=1,#distancekeys do -local nearestGroup=nearestList[distancekeys[i]] -local groupType=string.match(nearestGroup:GetName(),"(.+)-(.+)$") -local Cargotype=nil -for k,v in pairs(self.Cargo_Troops)do -local comparison="" -if type(v.Templates)=="string"then comparison=v.Templates else comparison=v.Templates[1]end -if comparison==groupType then -Cargotype=v -break -end -end -if Cargotype==nil then -self:_SendMessage("Can't onboard "..groupType,10,false,Group) -else -local troopsize=Cargotype:GetCratesNeeded() -local numberonboard=0 -local loaded={} -if self.Loaded_Cargo[unitname]then -loaded=self.Loaded_Cargo[unitname] -numberonboard=loaded.Troopsloaded or 0 -else -loaded={} -loaded.Troopsloaded=0 -loaded.Cratesloaded=0 -loaded.Cargo={} -end -if troopsize+numberonboard>trooplimit then -self:_SendMessage("Sorry, we\'re crammed already!",10,false,Group) -else -self.CargoCounter=self.CargoCounter+1 -local loadcargotype=CTLD_CARGO:New(self.CargoCounter,Cargotype.Name,Cargotype.Templates,Cargotype.CargoType,true,true,Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) -self:T({cargotype=loadcargotype}) -loaded.Troopsloaded=loaded.Troopsloaded+troopsize -table.insert(loaded.Cargo,loadcargotype) -self.Loaded_Cargo[unitname]=loaded -self:_SendMessage("Troops boarded!",10,false,Group) -self:_UpdateUnitCargoMass(Unit) -self:__TroopsExtracted(1,Group,Unit,nearestGroup) -if type(Cargotype.Templates)=="table"and Cargotype.Templates[2]then -for _,_key in pairs(Cargotype.Templates)do -table.insert(secondarygroups,_key) -end -end -nearestGroup:Destroy(false) -end -end -end -for _,_name in pairs(secondarygroups)do -for _,_group in pairs(nearestList)do -if _group and _group:IsAlive()then -local groupname=string.match(_group:GetName(),"(.+)-(.+)$") -if _name==groupname then -_group:Destroy(false) -end -end -end -end -self:CleanDroppedTroops() -return self -end -function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack) -self:T(self.lid.." _GetCrates") -if not drop and not pack then -local cgoname=Cargo:GetName() -local instock=Cargo:GetStock() -if type(instock)=="number"and tonumber(instock)<=0 and tonumber(instock)~=-1 then -self:_SendMessage(string.format("Sorry, we ran out of %s",cgoname),10,false,Group) -return self -end -end -local inzone=false -local drop=drop or false -local ship=nil -local width=20 -local distance=nil -local zone=nil -if not drop and not pack then -inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) -if not inzone then -inzone,ship,zone,distance,width=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) -end -elseif drop and not pack then -if self.dropcratesanywhere then -inzone=true -else -inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) -end -elseif pack and not drop then -inzone=true -end -if not inzone then -self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) -if not self.debug then return self end -end -local capabilities=self:_GetUnitCapabilities(Unit) -local canloadcratesno=capabilities.cratelimit -local loaddist=self.CrateDistance or 35 -local nearcrates,numbernearby=self:_FindCratesNearby(Group,Unit,loaddist,true) -if numbernearby>=canloadcratesno and not drop then -self:_SendMessage("There are enough crates nearby already! Take care of those first!",10,false,Group) -return self -end -local IsHerc=self:IsHercules(Unit) -local cargotype=Cargo -local number=number or cargotype:GetCratesNeeded() -local cratesneeded=cargotype:GetCratesNeeded() -local cratename=cargotype:GetName() -local cratetemplate="Container" -local cgotype=cargotype:GetType() -local cgomass=cargotype:GetMass() -local isstatic=false -if cgotype==CTLD_CARGO.Enum.STATIC then -cratetemplate=cargotype:GetTemplates() -isstatic=true -end -local position=Unit:GetCoordinate() -local heading=Unit:GetHeading()+1 -local height=Unit:GetHeight() -local droppedcargo={} -local cratedistance=0 -local rheading=0 -local angleOffNose=0 -local addon=0 -if IsHerc then -addon=180 -end -for i=1,number do -local cratealias=string.format("%s-%s-%d",cratename,cratetemplate,math.random(1,100000)) -if not self.placeCratesAhead then -cratedistance=(i-1)*2.5+capabilities.length -if cratedistance>self.CrateDistance then cratedistance=self.CrateDistance end -rheading=UTILS.RandomGaussian(0,30,-90,90,100) -rheading=math.fmod((heading+rheading+addon),360) -else -local initialSpacing=IsHerc and 16 or(capabilities.length+2) -local crateSpacing=4 -local lateralSpacing=4 -local nrSideBySideCrates=3 -if cratesneeded==1 then -cratedistance=initialSpacing -rheading=heading -else -if(i-1)%nrSideBySideCrates==0 then -cratedistance=i==1 and initialSpacing or cratedistance+crateSpacing -angleOffNose=math.ceil(math.deg(math.atan(lateralSpacing/cratedistance))) -rheading=heading-angleOffNose -else -rheading=rheading+angleOffNose -end -end -end -local cratecoord=position:Translate(cratedistance,rheading) -local cratevec2=cratecoord:GetVec2() -self.CrateCounter=self.CrateCounter+1 -local basetype=self.basetype or"container_cargo" -if isstatic then -basetype=cratetemplate -end -if type(ship)=="string"then -self:T("Spawning on ship "..ship) -local Ship=UNIT:FindByName(ship) -local shipcoord=Ship:GetCoordinate() -local unitcoord=Unit:GetCoordinate() -local dist=shipcoord:Get2DDistance(unitcoord) -dist=dist-(20+math.random(1,10)) -local width=width/2 -local Offy=math.random(-width,width) -self.Spawned_Crates[self.CrateCounter]=SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) -:InitCargoMass(cgomass) -:InitCargo(self.enableslingload) -:InitLinkToUnit(Ship,dist,Offy,0) -:Spawn(270,cratealias) -else -self.Spawned_Crates[self.CrateCounter]=SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) -:InitCoordinate(cratecoord) -:InitCargoMass(cgomass) -:InitCargo(self.enableslingload) -:Spawn(270,cratealias) -end -local templ=cargotype:GetTemplates() -local sorte=cargotype:GetType() -local subcat=cargotype.Subcategory -self.CargoCounter=self.CargoCounter+1 -local realcargo=nil -if drop then -realcargo=CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat) -table.insert(droppedcargo,realcargo) -else -realcargo=CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat) -end -table.insert(self.Spawned_Cargo,realcargo) -end -if not(drop or pack)then -Cargo:RemoveStock() -end -local text=string.format("Crates for %s have been positioned near you!",cratename) -if drop then -text=string.format("Crates for %s have been dropped!",cratename) -self:__CratesDropped(1,Group,Unit,droppedcargo) -end -self:_SendMessage(text,10,false,Group) -return self -end -function CTLD:InjectStatics(Zone,Cargo,RandomCoord) -self:T(self.lid.." InjectStatics") -local cratecoord=Zone:GetCoordinate() -if RandomCoord then -cratecoord=Zone:GetRandomCoordinate(5,20) -end -local surface=cratecoord:GetSurfaceType() -if surface==land.SurfaceType.WATER then -return self -end -local cargotype=Cargo -local cratesneeded=cargotype:GetCratesNeeded() -local cratetemplate="Container" -local cratename=cargotype:GetName() -local cgotype=cargotype:GetType() -local cgomass=cargotype:GetMass() -local cratealias=string.format("%s-%s-%d",cratename,cratetemplate,math.random(1,100000)) -local isstatic=false -if cgotype==CTLD_CARGO.Enum.STATIC then -cratetemplate=cargotype:GetTemplates() -isstatic=true -end -local basetype=self.basetype or"container_cargo" -if isstatic then -basetype=cratetemplate -end -self.CrateCounter=self.CrateCounter+1 -self.Spawned_Crates[self.CrateCounter]=SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) -:InitCargoMass(cgomass) -:InitCargo(self.enableslingload) -:InitCoordinate(cratecoord) -:Spawn(270,cratealias) -local templ=cargotype:GetTemplates() -local sorte=cargotype:GetType() -self.CargoCounter=self.CargoCounter+1 -cargotype.Positionable=self.Spawned_Crates[self.CrateCounter] -table.insert(self.Spawned_Cargo,cargotype) -return self -end -function CTLD:InjectStaticFromTemplate(Zone,Template,Mass) -self:T(self.lid.." InjectStaticFromTemplate") -local cargotype=self:GetStaticsCargoFromTemplate(Template,Mass) -self:InjectStatics(Zone,cargotype,true) -return self -end -function CTLD:_ListCratesNearby(_group,_unit) -self:T(self.lid.." _ListCratesNearby") -local finddist=self.CrateDistance or 35 -local crates,number=self:_FindCratesNearby(_group,_unit,finddist,true) -if number>0 then -local text=REPORT:New("Crates Found Nearby:") -text:Add("------------------------------------------------------------") -for _,_entry in pairs(crates)do -local entry=_entry -local name=entry:GetName() -local dropped=entry:WasDropped() -if dropped then -text:Add(string.format("Dropped crate for %s, %dkg",name,entry.PerCrateMass)) -else -text:Add(string.format("Crate for %s, %dkg",name,entry.PerCrateMass)) -end -end -if text:GetCount()==1 then -text:Add(" N O N E") -end -text:Add("------------------------------------------------------------") -self:_SendMessage(text:Text(),30,true,_group) -else -self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) -end -return self -end -function CTLD:_RemoveCratesNearby(_group,_unit) -self:T(self.lid.." _RemoveCratesNearby") -local finddist=self.CrateDistance or 35 -local crates,number=self:_FindCratesNearby(_group,_unit,finddist,true) -if number>0 then -local text=REPORT:New("Removing Crates Found Nearby:") -text:Add("------------------------------------------------------------") -for _,_entry in pairs(crates)do -local entry=_entry -local name=entry:GetName() -local dropped=entry:WasDropped() -if dropped then -text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) -else -text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) -end -entry:GetPositionable():Destroy(false) -end -if text:GetCount()==1 then -text:Add(" N O N E") -end -text:Add("------------------------------------------------------------") -self:_SendMessage(text:Text(),30,true,_group) -else -self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) -end -return self -end -function CTLD:_GetDistance(_point1,_point2) -self:T(self.lid.." _GetDistance") -if _point1 and _point2 then -local distance1=_point1:Get2DDistance(_point2) -local distance2=_point1:DistanceFromPointVec2(_point2) -if distance1 and type(distance1)=="number"then -return distance1 -elseif distance2 and type(distance2)=="number"then -return distance2 -else -self:E("*****Cannot calculate distance!") -self:E({_point1,_point2}) -return-1 -end -else -self:E("******Cannot calculate distance!") -self:E({_point1,_point2}) -return-1 -end -end -function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight) -self:T(self.lid.." _FindCratesNearby") -local finddist=_dist -local location=_group:GetCoordinate() -local existingcrates=self.Spawned_Cargo -local index=0 -local found={} -local loadedmass=0 -local unittype="none" -local capabilities={} -local maxmass=2000 -local maxloadable=2000 -if not _ignoreweight then -maxloadable=self:_GetMaxLoadableMass(_unit) -end -self:T(self.lid.." Max loadable mass: "..maxloadable) -for _,_cargoobject in pairs(existingcrates)do -local cargo=_cargoobject -local static=cargo:GetPositionable() -local staticid=cargo:GetID() -local weight=cargo:GetMass() -self:T(self.lid.." Found cargo mass: "..weight) -if static and static:IsAlive()then -local staticpos=static:GetCoordinate() -local distance=self:_GetDistance(location,staticpos) -if distance<=finddist and static and(weight<=maxloadable or _ignoreweight)then -index=index+1 -table.insert(found,staticid,cargo) -maxloadable=maxloadable-weight -end -end -end -return found,index -end -function CTLD:_LoadCratesNearby(Group,Unit) -self:T(self.lid.." _LoadCratesNearby") -local group=Group -local unit=Unit -local unitname=unit:GetName() -local unittype=unit:GetTypeName() -local capabilities=self:_GetUnitCapabilities(Unit) -local cancrates=capabilities.crates -local cratelimit=capabilities.cratelimit -local grounded=not self:IsUnitInAir(Unit) -local canhoverload=self:CanHoverLoad(Unit) -if not cancrates then -self:_SendMessage("Sorry this chopper cannot carry crates!",10,false,Group) -elseif self.forcehoverload and not canhoverload then -self:_SendMessage("Hover over the crates to pick them up!",10,false,Group) -elseif not grounded and not canhoverload then -self:_SendMessage("Land or hover over the crates to pick them up!",10,false,Group) -else -local numberonboard=0 -local massonboard=0 -local loaded={} -if self.Loaded_Cargo[unitname]then -loaded=self.Loaded_Cargo[unitname] -numberonboard=loaded.Cratesloaded or 0 -massonboard=self:_GetUnitCargoMass(Unit) -else -loaded={} -loaded.Troopsloaded=0 -loaded.Cratesloaded=0 -loaded.Cargo={} -end -local finddist=self.CrateDistance or 35 -local nearcrates,number=self:_FindCratesNearby(Group,Unit,finddist,false) -self:T(self.lid.." Crates found: "..number) -if number==0 and self.hoverautoloading then -return self -elseif number==0 then -self:_SendMessage("Sorry no loadable crates nearby or max cargo weight reached!",10,false,Group) -return self -elseif numberonboard==cratelimit then -self:_SendMessage("Sorry no fully loaded!",10,false,Group) -return self -else -local capacity=cratelimit-numberonboard -local crateidsloaded={} -local loops=0 -while loaded.Cratesloadedcrateind and _crate.Positionable~=nil then -crateind=_crate:GetID() -end -else -if not _crate:HasMoved()and not _crate:WasDropped()and _crate:GetID()>crateind then -crateind=_crate:GetID() -end -end -end -if crateind>0 then -local crate=nearcrates[crateind] -loaded.Cratesloaded=loaded.Cratesloaded+1 -crate:SetHasMoved(true) -crate:SetWasDropped(false) -table.insert(loaded.Cargo,crate) -table.insert(crateidsloaded,crate:GetID()) -crate:GetPositionable():Destroy(false) -crate.Positionable=nil -self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,false,Group) -table.remove(nearcrates,crate:GetID()) -self:__CratesPickedUp(1,Group,Unit,crate) -end -end -self.Loaded_Cargo[unitname]=loaded -self:_UpdateUnitCargoMass(Unit) -self:_CleanupTrackedCrates(crateidsloaded) -end -end -return self -end -function CTLD:_CleanupTrackedCrates(crateIdsToRemove) -local existingcrates=self.Spawned_Cargo -local newexcrates={} -for _,_crate in pairs(existingcrates)do -local excrate=_crate -local ID=excrate:GetID() -local keep=true -for _,_ID in pairs(crateIdsToRemove)do -if ID==_ID then -keep=false -end -end -local static=_crate:GetPositionable() -if not static or not static:IsAlive()then -keep=false -end -if keep then -table.insert(newexcrates,_crate) -end -end -self.Spawned_Cargo=nil -self.Spawned_Cargo=newexcrates -return self -end -function CTLD:_GetUnitCargoMass(Unit) -self:T(self.lid.." _GetUnitCargoMass") -if not Unit then return 0 end -local unitname=Unit:GetName() -local loadedcargo=self.Loaded_Cargo[unitname]or{} -local loadedmass=0 -if self.Loaded_Cargo[unitname]then -local cargotable=loadedcargo.Cargo or{} -for _,_cargo in pairs(cargotable)do -local cargo=_cargo -local type=cargo:GetType() -if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then -loadedmass=loadedmass+(cargo.PerCrateMass*cargo:GetCratesNeeded()) -end -if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped()then -loadedmass=loadedmass+cargo.PerCrateMass -end -end -end -return loadedmass -end -function CTLD:_GetMaxLoadableMass(Unit) -self:T(self.lid.." _GetMaxLoadableMass") -if not Unit then return 0 end -local loadable=0 -local loadedmass=self:_GetUnitCargoMass(Unit) -local capabilities=self:_GetUnitCapabilities(Unit) -local maxmass=capabilities.cargoweightlimit or 2000 -loadable=maxmass-loadedmass -return loadable -end -function CTLD:_UpdateUnitCargoMass(Unit) -self:T(self.lid.." _UpdateUnitCargoMass") -local calculatedMass=self:_GetUnitCargoMass(Unit) -Unit:SetUnitInternalCargo(calculatedMass) -return self -end -function CTLD:_ListCargo(Group,Unit) -self:T(self.lid.." _ListCargo") -local unitname=Unit:GetName() -local unittype=Unit:GetTypeName() -local capabilities=self:_GetUnitCapabilities(Unit) -local trooplimit=capabilities.trooplimit -local cratelimit=capabilities.cratelimit -local loadedcargo=self.Loaded_Cargo[unitname]or{} -local loadedmass=self:_GetUnitCargoMass(Unit) -local maxloadable=self:_GetMaxLoadableMass(Unit) -if self.Loaded_Cargo[unitname]then -local no_troops=loadedcargo.Troopsloaded or 0 -local no_crates=loadedcargo.Cratesloaded or 0 -local cargotable=loadedcargo.Cargo or{} -local report=REPORT:New("Transport Checkout Sheet") -report:Add("------------------------------------------------------------") -report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit)) -report:Add("------------------------------------------------------------") -report:Add(" -- TROOPS --") -for _,_cargo in pairs(cargotable)do -local cargo=_cargo -local type=cargo:GetType() -if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and(not cargo:WasDropped()or self.allowcratepickupagain)then -report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded())) -end -end -if report:GetCount()==4 then -report:Add(" N O N E") -end -report:Add("------------------------------------------------------------") -report:Add(" -- CRATES --") -local cratecount=0 -for _,_cargo in pairs(cargotable)do -local cargo=_cargo -local type=cargo:GetType() -if(type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS)and(not cargo:WasDropped()or self.allowcratepickupagain)then -report:Add(string.format("Crate: %s size 1",cargo:GetName())) -cratecount=cratecount+1 -end -end -if cratecount==0 then -report:Add(" N O N E") -end -report:Add("------------------------------------------------------------") -report:Add("Total Mass: "..loadedmass.." kg. Loadable: "..maxloadable.." kg.") -local text=report:Text() -self:_SendMessage(text,30,true,Group) -else -self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs",trooplimit,cratelimit,maxloadable),10,false,Group) -end -return self -end -function CTLD:_ListInventory(Group,Unit) -self:T(self.lid.." _ListInventory") -local unitname=Unit:GetName() -local unittype=Unit:GetTypeName() -local cgotypes=self.Cargo_Crates -local trptypes=self.Cargo_Troops -local stctypes=self.Cargo_Statics -local function countcargo(cgotable) -local counter=0 -for _,_cgo in pairs(cgotable)do -counter=counter+1 -end -return counter -end -local crateno=countcargo(cgotypes) -local troopno=countcargo(trptypes) -local staticno=countcargo(stctypes) -if(crateno>0 or troopno>0 or staticno>0)then -local report=REPORT:New("Inventory Sheet") -report:Add("------------------------------------------------------------") -report:Add(string.format("Troops: %d, Cratetypes: %d",troopno,crateno+staticno)) -report:Add("------------------------------------------------------------") -report:Add(" -- TROOPS --") -for _,_cargo in pairs(trptypes)do -local cargo=_cargo -local type=cargo:GetType() -if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then -local stockn=cargo:GetStock() -local stock="none" -if stockn==-1 then -stock="unlimited" -elseif stockn>0 then -stock=tostring(stockn) -end -report:Add(string.format("Unit: %s | Soldiers: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock)) -end -end -if report:GetCount()==4 then -report:Add(" N O N E") -end -report:Add("------------------------------------------------------------") -report:Add(" -- CRATES --") -local cratecount=0 -for _,_cargo in pairs(cgotypes)do -local cargo=_cargo -local type=cargo:GetType() -if(type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then -local stockn=cargo:GetStock() -local stock="none" -if stockn==-1 then -stock="unlimited" -elseif stockn>0 then -stock=tostring(stockn) -end -report:Add(string.format("Type: %s | Crates per Set: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock)) -cratecount=cratecount+1 -end -end -for _,_cargo in pairs(stctypes)do -local cargo=_cargo -local type=cargo:GetType() -if(type==CTLD_CARGO.Enum.STATIC)and not cargo:WasDropped()then -local stockn=cargo:GetStock() -local stock="none" -if stockn==-1 then -stock="unlimited" -elseif stockn>0 then -stock=tostring(stockn) -end -report:Add(string.format("Type: %s | Stock: %s",cargo:GetName(),stock)) -cratecount=cratecount+1 -end -end -if cratecount==0 then -report:Add(" N O N E") -end -local text=report:Text() -self:_SendMessage(text,30,true,Group) -else -self:_SendMessage(string.format("Nothing in stock!"),10,false,Group) -end -return self -end -function CTLD:IsHercules(Unit) -if Unit:GetTypeName()=="Hercules"or string.find(Unit:GetTypeName(),"Bronco")then -return true -else -return false -end -end -function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template) -local Positions={} -local template=_DATABASE:GetGroupTemplate(Template) -UTILS.PrintTableToLog(template) -local numbertroops=#template.units -local newcenter=Coordinate:Translate(Radius,((Heading+270)%360)) -for i=1,360,math.floor(360/numbertroops)do -local phead=((Heading+270+i)%360) -local post=newcenter:Translate(Radius,phead) -local pos1=post:GetVec2() -local p1t={ -x=pos1.x, -y=pos1.y, -heading=phead, -} -table.insert(Positions,p1t) -end -UTILS.PrintTableToLog(Positions) -return Positions -end -function CTLD:_UnloadTroops(Group,Unit) -self:T(self.lid.." _UnloadTroops") -local droppingatbase=false -local canunload=true -if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then -self:_SendMessage("You need to open the door(s) to unload troops!",10,false,Group) -if not self.debug then return self end -end -local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) -if not inzone then -inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) -end -if inzone then -droppingatbase=true -end -local hoverunload=self:IsCorrectHover(Unit) -local IsHerc=self:IsHercules(Unit) -if IsHerc then -hoverunload=self:IsCorrectFlightParameters(Unit) -end -local grounded=not self:IsUnitInAir(Unit) -local unitname=Unit:GetName() -if self.Loaded_Cargo[unitname]and(grounded or hoverunload)then -if not droppingatbase or self.debug then -local loadedcargo=self.Loaded_Cargo[unitname]or{} -local cargotable=loadedcargo.Cargo -for _,_cargo in pairs(cargotable)do -local cargo=_cargo -local type=cargo:GetType() -if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then -local name=cargo:GetName()or"none" -local temptable=cargo:GetTemplates()or{} -local position=Group:GetCoordinate() -local zoneradius=self.troopdropzoneradius or 100 -local factor=1 -if IsHerc then -factor=cargo:GetCratesNeeded()or 1 -zoneradius=Unit:GetVelocityMPS()or 100 -end -local zone=ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,zoneradius*factor) -local randomcoord=zone:GetRandomCoordinate(10,30*factor) -local heading=Group:GetHeading()or 0 -if hoverunload or grounded then -randomcoord=Group:GetCoordinate() -local Angle=(heading+270)%360 -local offset=hoverunload and 1.5 or 5 -randomcoord:Translate(offset,Angle,nil,true) -end -local tempcount=0 -for _,_template in pairs(temptable)do -self.TroopCounter=self.TroopCounter+1 -tempcount=tempcount+1 -local alias=string.format("%s-%d",_template,math.random(1,100000)) -local rad=2.5+tempcount -local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template) -self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) -:InitDelayOff() -:InitSetUnitAbsolutePositions(Positions) -:SpawnFromVec2(randomcoord:GetVec2()) -self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],type) -end -cargo:SetWasDropped(true) -if type==CTLD_CARGO.Enum.ENGINEERS then -self.Engineers=self.Engineers+1 -local grpname=self.DroppedTroops[self.TroopCounter]:GetName() -self.EngineersInField[self.Engineers]=CTLD_ENGINEERING:New(name,grpname) -self:_SendMessage(string.format("Dropped Engineers %s into action!",name),10,false,Group) -else -self:_SendMessage(string.format("Dropped Troops %s into action!",name),10,false,Group) -end -end -end -else -self:_SendMessage("Troops have returned to base!",10,false,Group) -self:__TroopsRTB(1,Group,Unit) -end -local loaded={} -loaded.Troopsloaded=0 -loaded.Cratesloaded=0 -loaded.Cargo={} -local loadedcargo=self.Loaded_Cargo[unitname]or{} -local cargotable=loadedcargo.Cargo or{} -for _,_cargo in pairs(cargotable)do -local cargo=_cargo -local type=cargo:GetType() -local dropped=cargo:WasDropped() -if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and not dropped then -table.insert(loaded.Cargo,_cargo) -loaded.Cratesloaded=loaded.Cratesloaded+1 -else -if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and droppingatbase then -local name=cargo:GetName() -local gentroops=self.Cargo_Troops -for _id,_troop in pairs(gentroops)do -if _troop.Name==name then -local stock=_troop:GetStock() -if stock and tonumber(stock)>=0 then _troop:AddStock()end -end -end -end -end -end -self.Loaded_Cargo[unitname]=nil -self.Loaded_Cargo[unitname]=loaded -self:_UpdateUnitCargoMass(Unit) -else -if IsHerc then -self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) -else -self:_SendMessage("Nothing loaded or not hovering within parameters!",10,false,Group) -end -end -return self -end -function CTLD:_UnloadCrates(Group,Unit) -self:T(self.lid.." _UnloadCrates") -if not self.dropcratesanywhere then -local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) -if not inzone then -self:_SendMessage("You are not close enough to a drop zone!",10,false,Group) -if not self.debug then -return self -end -end -end -local hoverunload=self:IsCorrectHover(Unit) -local IsHerc=self:IsHercules(Unit) -if IsHerc then -hoverunload=self:IsCorrectFlightParameters(Unit) -end -local grounded=not self:IsUnitInAir(Unit) -local unitname=Unit:GetName() -if self.Loaded_Cargo[unitname]and(grounded or hoverunload)then -local loadedcargo=self.Loaded_Cargo[unitname]or{} -local cargotable=loadedcargo.Cargo -for _,_cargo in pairs(cargotable)do -local cargo=_cargo -local type=cargo:GetType() -if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and(not cargo:WasDropped()or self.allowcratepickupagain)then -self:_GetCrates(Group,Unit,cargo,1,true) -cargo:SetWasDropped(true) -cargo:SetHasMoved(true) -end -end -local loaded={} -loaded.Troopsloaded=0 -loaded.Cratesloaded=0 -loaded.Cargo={} -for _,_cargo in pairs(cargotable)do -local cargo=_cargo -local type=cargo:GetType() -local size=cargo:GetCratesNeeded() -if type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS then -table.insert(loaded.Cargo,_cargo) -loaded.Troopsloaded=loaded.Troopsloaded+size -end -end -self.Loaded_Cargo[unitname]=nil -self.Loaded_Cargo[unitname]=loaded -self:_UpdateUnitCargoMass(Unit) -else -if IsHerc then -self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) -else -self:_SendMessage("Nothing loaded or not hovering within parameters!",10,false,Group) -end -end -return self -end -function CTLD:_BuildCrates(Group,Unit,Engineering) -self:T(self.lid.." _BuildCrates") -if self:IsHercules(Unit)and self.enableHercules and not Engineering then -local speed=Unit:GetVelocityKMH() -if speed>1 then -self:_SendMessage("You need to land / stop to build something, Pilot!",10,false,Group) -return self -end -end -if not Engineering and self.nobuildinloadzones then -local inloadzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) -if inloadzone then -self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) -return self -end -end -local finddist=self.CrateDistance or 35 -local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true) -local buildables={} -local foundbuilds=false -local canbuild=false -if number>0 then -for _,_crate in pairs(crates)do -local Crate=_crate -if(Crate:WasDropped()or not self.movecratesbeforebuild)and not Crate:IsRepair()and not Crate:IsStatic()then -local name=Crate:GetName() -local required=Crate:GetCratesNeeded() -local template=Crate:GetTemplates() -local ctype=Crate:GetType() -local ccoord=Crate:GetPositionable():GetCoordinate() -if not buildables[name]then -local object={} -object.Name=name -object.Required=required -object.Found=1 -object.Template=template -object.CanBuild=false -object.Type=ctype -object.Coord=ccoord:GetVec2() -buildables[name]=object -foundbuilds=true -else -buildables[name].Found=buildables[name].Found+1 -foundbuilds=true -end -if buildables[name].Found>=buildables[name].Required then -buildables[name].CanBuild=true -canbuild=true -end -self:T({buildables=buildables}) -end -end -local report=REPORT:New("Checklist Buildable Crates") -report:Add("------------------------------------------------------------") -for _,_build in pairs(buildables)do -local build=_build -local name=build.Name -local needed=build.Required -local found=build.Found -local txtok="NO" -if build.CanBuild then -txtok="YES" -end -local text=string.format("Type: %s | Required %d | Found %d | Can Build %s",name,needed,found,txtok) -report:Add(text) -end -if not foundbuilds then -report:Add(" --- None found! ---") -if self.movecratesbeforebuild then -report:Add("*** Crates need to be moved before building!") -end -end -report:Add("------------------------------------------------------------") -local text=report:Text() -if not Engineering then -self:_SendMessage(text,30,true,Group) -else -self:T(text) -end -if canbuild then -for _,_build in pairs(buildables)do -local build=_build -if build.CanBuild then -self:_CleanUpCrates(crates,build,number) -if self.buildtime and self.buildtime>0 then -local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate()) -buildtimer:Start(self.buildtime) -self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) -self:__CratesBuildStarted(1,Group,Unit) -else -self:_BuildObjectFromCrates(Group,Unit,build) -end -end -end -end -else -if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist),10,false,Group)end -end -return self -end -function CTLD:_PackCratesNearby(Group,Unit) -self:T(self.lid.." _PackCratesNearby") -local location=Group:GetCoordinate() -local nearestGroups=SET_GROUP:New():FilterCoalitions("blue"):FilterZones({ZONE_RADIUS:New("TempZone",location:GetVec2(),self.PackDistance,false)}):FilterOnce() -for _,_Group in pairs(nearestGroups.Set)do -for _,_Template in pairs(_DATABASE.Templates.Groups)do -if(string.match(_Group:GetName(),_Template.GroupName))then -for _,_entry in pairs(self.Cargo_Crates)do -if(_entry.Templates[1]==_Template.GroupName)then -_Group:Destroy() -self:_GetCrates(Group,Unit,_entry,nil,false,true) -return self -end -end -end -end -end -return self -end -function CTLD:_RepairCrates(Group,Unit,Engineering) -self:T(self.lid.." _RepairCrates") -local finddist=self.CrateDistance or 35 -local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true) -local buildables={} -local foundbuilds=false -local canbuild=false -if number>0 then -for _,_crate in pairs(crates)do -local Crate=_crate -if Crate:WasDropped()and Crate:IsRepair()and not Crate:IsStatic()then -local name=Crate:GetName() -local required=Crate:GetCratesNeeded() -local template=Crate:GetTemplates() -local ctype=Crate:GetType() -if not buildables[name]then -local object={} -object.Name=name -object.Required=required -object.Found=1 -object.Template=template -object.CanBuild=false -object.Type=ctype -buildables[name]=object -foundbuilds=true -else -buildables[name].Found=buildables[name].Found+1 -foundbuilds=true -end -if buildables[name].Found>=buildables[name].Required then -buildables[name].CanBuild=true -canbuild=true -end -self:T({repair=buildables}) -end -end -local report=REPORT:New("Checklist Repairs") -report:Add("------------------------------------------------------------") -for _,_build in pairs(buildables)do -local build=_build -local name=build.Name -local needed=build.Required -local found=build.Found -local txtok="NO" -if build.CanBuild then -txtok="YES" -end -local text=string.format("Type: %s | Required %d | Found %d | Can Repair %s",name,needed,found,txtok) -report:Add(text) -end -if not foundbuilds then report:Add(" --- None Found ---")end -report:Add("------------------------------------------------------------") -local text=report:Text() -if not Engineering then -self:_SendMessage(text,30,true,Group) -else -self:T(text) -end -if canbuild then -for _,_build in pairs(buildables)do -local build=_build -if build.CanBuild then -self:_RepairObjectFromCrates(Group,Unit,crates,build,number,Engineering) -end -end -end -else -if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist),10,false,Group)end -end -return self -end -function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) -self:T(self.lid.." _BuildObjectFromCrates") -if Group and Group:IsAlive()or(RepairLocation and not Repair)then -local name=Build.Name -local ctype=Build.Type -local canmove=false -if ctype==CTLD_CARGO.Enum.VEHICLE then canmove=true end -if ctype==CTLD_CARGO.Enum.STATIC then -return self -end -local temptable=Build.Template or{} -if type(temptable)=="string"then -temptable={temptable} -end -local zone=nil -if RepairLocation and not Repair then -zone=ZONE_RADIUS:New(string.format("Build zone-%d",math.random(1,10000)),RepairLocation:GetVec2(),100) -else -zone=ZONE_GROUP:New(string.format("Unload zone-%d",math.random(1,10000)),Group,100) -end -local randomcoord=Build.Coord or zone:GetRandomCoordinate(35):GetVec2() -if Repair then -randomcoord=RepairLocation:GetVec2() -end -for _,_template in pairs(temptable)do -self.TroopCounter=self.TroopCounter+1 -local alias=string.format("%s-%d",_template,math.random(1,100000)) -if canmove then -self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) -:InitDelayOff() -:SpawnFromVec2(randomcoord) -else -self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) -:InitDelayOff() -:SpawnFromVec2(randomcoord) -end -if Repair then -self:__CratesRepaired(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) -else -self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) -end -end -else -self:T(self.lid.."Group KIA while building!") -end -return self -end -function CTLD:_MoveGroupToZone(Group) -self:T(self.lid.." _MoveGroupToZone") -local groupname=Group:GetName()or"none" -local groupcoord=Group:GetCoordinate() -local outcome,name,zone,distance=self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) -if(distance<=self.movetroopsdistance)and zone then -local groupname=Group:GetName() -local zonecoord=zone:GetRandomCoordinate(20,125) -local coordinate=zonecoord:GetVec2() -Group:SetAIOn() -Group:OptionAlarmStateAuto() -Group:OptionDisperseOnAttack(30) -Group:OptionROEOpenFirePossible() -Group:RouteToVec2(coordinate,5) -end -return self -end -function CTLD:_CleanUpCrates(Crates,Build,Number) -self:T(self.lid.." _CleanUpCrates") -local build=Build -local existingcrates=self.Spawned_Cargo -local newexcrates={} -local numberdest=Build.Required -local nametype=Build.Name -local found=0 -local rounds=Number -local destIDs={} -for _,_crate in pairs(Crates)do -local nowcrate=_crate -local name=nowcrate:GetName() -local thisID=nowcrate:GetID() -if name==nametype then -table.insert(destIDs,thisID) -found=found+1 -nowcrate:GetPositionable():Destroy(false) -nowcrate.Positionable=nil -nowcrate.HasBeenDropped=false -end -if found==numberdest then break end -end -self:_CleanupTrackedCrates(destIDs) -return self -end -function CTLD:_RefreshF10Menus() -self:T(self.lid.." _RefreshF10Menus") -local PlayerSet=self.PilotGroups -local PlayerTable=PlayerSet:GetSetObjects() -local _UnitList={} -for _key,_group in pairs(PlayerTable)do -local _unit=_group:GetUnit(1) -if _unit then -if _unit:IsAlive()and _unit:IsPlayer()then -if _unit:IsHelicopter()or(self:IsHercules(_unit)and self.enableHercules)then -local unitName=_unit:GetName() -_UnitList[unitName]=unitName -else -local unitName=_unit:GetName() -_UnitList[unitName]=nil -end -end -end -end -self.CtldUnits=_UnitList -if self.usesubcats then -for _id,_cargo in pairs(self.Cargo_Crates)do -local entry=_cargo -if not self.subcats[entry.Subcategory]then -self.subcats[entry.Subcategory]=entry.Subcategory -end -end -for _id,_cargo in pairs(self.Cargo_Statics)do -local entry=_cargo -if not self.subcats[entry.Subcategory]then -self.subcats[entry.Subcategory]=entry.Subcategory -end -end -for _id,_cargo in pairs(self.Cargo_Troops)do -local entry=_cargo -if not self.subcatsTroop[entry.Subcategory]then -self.subcatsTroop[entry.Subcategory]=entry.Subcategory -end -end -end -local menucount=0 -local menus={} -for _,_unitName in pairs(self.CtldUnits)do -if not self.MenusDone[_unitName]then -local _unit=UNIT:FindByName(_unitName) -if _unit then -local _group=_unit:GetGroup() -if _group then -local unittype=_unit:GetTypeName() -local capabilities=self:_GetUnitCapabilities(_unit) -local cantroops=capabilities.troops -local cancrates=capabilities.crates -local topmenu=MENU_GROUP:New(_group,"CTLD",nil) -local toptroops=nil -local topcrates=nil -if cantroops then -toptroops=MENU_GROUP:New(_group,"Manage Troops",topmenu) -end -if cancrates then -topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu) -end -local listmenu=MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit) -local invtry=MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit) -local rbcns=MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu,self._ListRadioBeacons,self,_group,_unit) -local smoketopmenu=MENU_GROUP:New(_group,"Smokes, Flares, Beacons",topmenu) -local smokemenu=MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu,self.SmokeZoneNearBy,self,_unit,false) -local smokeself=MENU_GROUP:New(_group,"Drop smoke now",smoketopmenu) -local smokeselfred=MENU_GROUP_COMMAND:New(_group,"Red smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Red) -local smokeselfblue=MENU_GROUP_COMMAND:New(_group,"Blue smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Blue) -local smokeselfgreen=MENU_GROUP_COMMAND:New(_group,"Green smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Green) -local smokeselforange=MENU_GROUP_COMMAND:New(_group,"Orange smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Orange) -local smokeselfwhite=MENU_GROUP_COMMAND:New(_group,"White smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.White) -local flaremenu=MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu,self.SmokeZoneNearBy,self,_unit,true) -local flareself=MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu,self.SmokePositionNow,self,_unit,true) -local beaconself=MENU_GROUP_COMMAND:New(_group,"Drop beacon now",smoketopmenu,self.DropBeaconNow,self,_unit):Refresh() -if cantroops then -local troopsmenu=MENU_GROUP:New(_group,"Load troops",toptroops) -if self.usesubcats then -local subcatmenus={} -for _name,_entry in pairs(self.subcatsTroop)do -subcatmenus[_name]=MENU_GROUP:New(_group,_name,troopsmenu) -end -for _,_entry in pairs(self.Cargo_Troops)do -local entry=_entry -local subcat=entry.Subcategory -menucount=menucount+1 -menus[menucount]=MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops,self,_group,_unit,entry) -end -else -for _,_entry in pairs(self.Cargo_Troops)do -local entry=_entry -menucount=menucount+1 -menus[menucount]=MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops,self,_group,_unit,entry) -end -end -local unloadmenu1=MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops,self._UnloadTroops,self,_group,_unit):Refresh() -local extractMenu1=MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh() -end -if cancrates then -local loadmenu=MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates,self._LoadCratesNearby,self,_group,_unit) -local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates) -local packmenu=MENU_GROUP_COMMAND:New(_group,"Pack crates",topcrates,self._PackCratesNearby,self,_group,_unit) -local removecratesmenu=MENU_GROUP:New(_group,"Remove crates",topcrates) -if self.usesubcats then -local subcatmenus={} -for _name,_entry in pairs(self.subcats)do -subcatmenus[_name]=MENU_GROUP:New(_group,_name,cratesmenu) -end -for _,_entry in pairs(self.Cargo_Crates)do -local entry=_entry -local subcat=entry.Subcategory -menucount=menucount+1 -local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) -menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates,self,_group,_unit,entry) -end -for _,_entry in pairs(self.Cargo_Statics)do -local entry=_entry -local subcat=entry.Subcategory -menucount=menucount+1 -local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) -menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates,self,_group,_unit,entry) -end -else -for _,_entry in pairs(self.Cargo_Crates)do -local entry=_entry -menucount=menucount+1 -local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) -menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates,self,_group,_unit,entry) -end -for _,_entry in pairs(self.Cargo_Statics)do -local entry=_entry -menucount=menucount+1 -local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) -menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates,self,_group,_unit,entry) -end -end -listmenu=MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit) -removecrates=MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu,self._RemoveCratesNearby,self,_group,_unit) -local unloadmenu=MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates,self._UnloadCrates,self,_group,_unit) -if not self.nobuildmenu then -local buildmenu=MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates,self._BuildCrates,self,_group,_unit) -local repairmenu=MENU_GROUP_COMMAND:New(_group,"Repair",topcrates,self._RepairCrates,self,_group,_unit):Refresh() -else -unloadmenu:Refresh() -end -end -if self:IsHercules(_unit)then -local hoverpars=MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu,self._ShowFlightParams,self,_group,_unit):Refresh() -else -local hoverpars=MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu,self._ShowHoverParams,self,_group,_unit):Refresh() -end -self.MenusDone[_unitName]=true -end -end -else -self:T(self.lid.." Menus already done for this group!") -end -end -return self -end -function CTLD:_CheckTemplates(temptable) -self:T(self.lid.." _CheckTemplates") -local outcome=true -if type(temptable)~="table"then -temptable={temptable} -end -for _,_name in pairs(temptable)do -if not _DATABASE.Templates.Groups[_name]then -outcome=false -self:E(self.lid.."ERROR: Template name ".._name.." is missing!") -end -end -return outcome -end -function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock,SubCategory) -self:T(self.lid.." AddTroopsCargo") -self:T({Name,Templates,Type,NoTroops,PerTroopMass,Stock}) -if not self:_CheckTemplates(Templates)then -self:E(self.lid.."Troops Cargo for "..Name.." has missing template(s)!") -return self -end -self.CargoCounter=self.CargoCounter+1 -local cargo=CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock,SubCategory) -table.insert(self.Cargo_Troops,cargo) -return self -end -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory) -self:T(self.lid.." AddCratesCargo") -if not self:_CheckTemplates(Templates)then -self:E(self.lid.."Crates Cargo for "..Name.." has missing template(s)!") -return self -end -self.CargoCounter=self.CargoCounter+1 -local cargo=CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory) -table.insert(self.Cargo_Crates,cargo) -return self -end -function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory) -self:T(self.lid.." AddStaticsCargo") -self.CargoCounter=self.CargoCounter+1 -local type=CTLD_CARGO.Enum.STATIC -local template=STATIC:FindByName(Name,true):GetTypeName() -local cargo=CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory) -table.insert(self.Cargo_Statics,cargo) -return self -end -function CTLD:GetStaticsCargoFromTemplate(Name,Mass) -self:T(self.lid.." GetStaticsCargoFromTemplate") -self.CargoCounter=self.CargoCounter+1 -local type=CTLD_CARGO.Enum.STATIC -local template=STATIC:FindByName(Name,true):GetTypeName() -local cargo=CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,1) -return cargo -end -function CTLD:AddCratesRepair(Name,Template,Type,NoCrates,PerCrateMass,Stock,SubCategory) -self:T(self.lid.." AddCratesRepair") -if not self:_CheckTemplates(Template)then -self:E(self.lid.."Repair Cargo for "..Name.." has a missing template!") -return self -end -self.CargoCounter=self.CargoCounter+1 -local cargo=CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory) -table.insert(self.Cargo_Crates,cargo) -return self -end -function CTLD:AddZone(Zone) -self:T(self.lid.." AddZone") -local zone=Zone -if zone.type==CTLD.CargoZoneType.LOAD then -table.insert(self.pickupZones,zone) -elseif zone.type==CTLD.CargoZoneType.DROP then -table.insert(self.dropOffZones,zone) -elseif zone.type==CTLD.CargoZoneType.SHIP then -table.insert(self.shipZones,zone) -elseif zone.type==CTLD.CargoZoneType.BEACON then -table.insert(self.droppedBeacons,zone) -else -table.insert(self.wpZones,zone) -end -return self -end -function CTLD:ActivateZone(Name,ZoneType,NewState) -self:T(self.lid.." ActivateZone") -local newstate=true -if NewState~=nil then -newstate=NewState -end -local table={} -if ZoneType==CTLD.CargoZoneType.LOAD then -table=self.pickupZones -elseif ZoneType==CTLD.CargoZoneType.DROP then -table=self.dropOffZones -elseif ZoneType==CTLD.CargoZoneType.SHIP then -table=self.shipZones -else -table=self.wpZones -end -for _,_zone in pairs(table)do -local thiszone=_zone -if thiszone.name==Name then -thiszone.active=newstate -break -end -end -return self -end -function CTLD:DeactivateZone(Name,ZoneType) -self:T(self.lid.." DeactivateZone") -self:ActivateZone(Name,ZoneType,false) -return self -end -function CTLD:_GetFMBeacon(Name) -self:T(self.lid.." _GetFMBeacon") -local beacon={} -if#self.FreeFMFrequencies<=1 then -self.FreeFMFrequencies=self.UsedFMFrequencies -self.UsedFMFrequencies={} -end -local FM=table.remove(self.FreeFMFrequencies,math.random(#self.FreeFMFrequencies)) -table.insert(self.UsedFMFrequencies,FM) -beacon.name=Name -beacon.frequency=FM/1000000 -beacon.modulation=CTLD.RadioModulation.FM -return beacon -end -function CTLD:_GetUHFBeacon(Name) -self:T(self.lid.." _GetUHFBeacon") -local beacon={} -if#self.FreeUHFFrequencies<=1 then -self.FreeUHFFrequencies=self.UsedUHFFrequencies -self.UsedUHFFrequencies={} -end -local UHF=table.remove(self.FreeUHFFrequencies,math.random(#self.FreeUHFFrequencies)) -table.insert(self.UsedUHFFrequencies,UHF) -beacon.name=Name -beacon.frequency=UHF/1000000 -beacon.modulation=CTLD.RadioModulation.AM -return beacon -end -function CTLD:_GetVHFBeacon(Name) -self:T(self.lid.." _GetVHFBeacon") -local beacon={} -if#self.FreeVHFFrequencies<=3 then -self.FreeVHFFrequencies=self.UsedVHFFrequencies -self.UsedVHFFrequencies={} -end -local VHF=table.remove(self.FreeVHFFrequencies,math.random(#self.FreeVHFFrequencies)) -table.insert(self.UsedVHFFrequencies,VHF) -beacon.name=Name -beacon.frequency=VHF/1000000 -beacon.modulation=CTLD.RadioModulation.FM -return beacon -end -function CTLD:AddCTLDZone(Name,Type,Color,Active,HasBeacon,Shiplength,Shipwidth) -self:T(self.lid.." AddCTLDZone") -local zone=ZONE:FindByName(Name) -if not zone and Type~=CTLD.CargoZoneType.SHIP then -self:E(self.lid.."**** Zone does not exist: "..Name) -return self -end -if Type==CTLD.CargoZoneType.SHIP then -local Ship=UNIT:FindByName(Name) -if not Ship then -self:E(self.lid.."**** Ship does not exist: "..Name) -return self -end -end -local ctldzone={} -ctldzone.active=Active or false -ctldzone.color=Color or SMOKECOLOR.Red -ctldzone.name=Name or"NONE" -ctldzone.type=Type or CTLD.CargoZoneType.MOVE -ctldzone.hasbeacon=HasBeacon or false -if Type==CTLD.CargoZoneType.BEACON then -self.droppedbeaconref[ctldzone.name]=zone:GetCoordinate() -ctldzone.timestamp=timer.getTime() -end -if HasBeacon then -ctldzone.fmbeacon=self:_GetFMBeacon(Name) -ctldzone.uhfbeacon=self:_GetUHFBeacon(Name) -ctldzone.vhfbeacon=self:_GetVHFBeacon(Name) -else -ctldzone.fmbeacon=nil -ctldzone.uhfbeacon=nil -ctldzone.vhfbeacon=nil -end -if Type==CTLD.CargoZoneType.SHIP then -ctldzone.shiplength=Shiplength or 100 -ctldzone.shipwidth=Shipwidth or 10 -end -self:AddZone(ctldzone) -return self -end -function CTLD:AddCTLDZoneFromAirbase(AirbaseName,Type,Color,Active,HasBeacon) -self:T(self.lid.." AddCTLDZoneFromAirbase") -local AFB=AIRBASE:FindByName(AirbaseName) -local name=AFB:GetZone():GetName() -self:T(self.lid.."AFB "..AirbaseName.." ZoneName "..name) -self:AddCTLDZone(name,Type,Color,Active,HasBeacon) -return self -end -function CTLD:DropBeaconNow(Unit) -self:T(self.lid.." DropBeaconNow") -local ctldzone={} -ctldzone.active=true -ctldzone.color=math.random(0,4) -ctldzone.name="Beacon "..math.random(1,10000) -ctldzone.type=CTLD.CargoZoneType.BEACON -ctldzone.hasbeacon=true -ctldzone.fmbeacon=self:_GetFMBeacon(ctldzone.name) -ctldzone.uhfbeacon=self:_GetUHFBeacon(ctldzone.name) -ctldzone.vhfbeacon=self:_GetVHFBeacon(ctldzone.name) -ctldzone.timestamp=timer.getTime() -self.droppedbeaconref[ctldzone.name]=Unit:GetCoordinate() -self:AddZone(ctldzone) -local FMbeacon=ctldzone.fmbeacon -local VHFbeacon=ctldzone.vhfbeacon -local UHFbeacon=ctldzone.uhfbeacon -local Name=ctldzone.name -local FM=FMbeacon.frequency -local VHF=VHFbeacon.frequency*1000 -local UHF=UHFbeacon.frequency -local text=string.format("Dropped %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",Name,FM,VHF,UHF) -self:_SendMessage(text,15,false,Unit:GetGroup()) -return self -end -function CTLD:CheckDroppedBeacons() -self:T(self.lid.." CheckDroppedBeacons") -local timeout=self.droppedbeacontimeout or 600 -local livebeacontable={} -for _,_beacon in pairs(self.droppedBeacons)do -local beacon=_beacon -if not beacon.timestamp then beacon.timestamp=timer.getTime()+timeout end -local T0=beacon.timestamp -if timer.getTime()-T0>timeout then -local name=beacon.name -self.droppedbeaconref[name]=nil -_beacon=nil -else -table.insert(livebeacontable,beacon) -end -end -self.droppedBeacons=nil -self.droppedBeacons=livebeacontable -return self -end -function CTLD:_ListRadioBeacons(Group,Unit) -self:T(self.lid.." _ListRadioBeacons") -local report=REPORT:New("Active Zone Beacons") -report:Add("------------------------------------------------------------") -local zones={[1]=self.pickupZones,[2]=self.wpZones,[3]=self.dropOffZones,[4]=self.shipZones,[5]=self.droppedBeacons} -for i=1,5 do -for index,cargozone in pairs(zones[i])do -local czone=cargozone -if czone.active and czone.hasbeacon then -local FMbeacon=czone.fmbeacon -local VHFbeacon=czone.vhfbeacon -local UHFbeacon=czone.uhfbeacon -local Name=czone.name -local FM=FMbeacon.frequency -local VHF=VHFbeacon.frequency*1000 -local UHF=UHFbeacon.frequency -report:AddIndent(string.format(" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",Name,FM,VHF,UHF),"|") -end -end -end -if report:GetCount()==1 then -report:Add(" N O N E") -end -report:Add("------------------------------------------------------------") -self:_SendMessage(report:Text(),30,true,Group) -return self -end -function CTLD:_AddRadioBeacon(Name,Sound,Mhz,Modulation,IsShip,IsDropped) -self:T(self.lid.." _AddRadioBeacon") -local Zone=nil -if IsShip then -Zone=UNIT:FindByName(Name) -elseif IsDropped then -Zone=self.droppedbeaconref[Name] -else -Zone=ZONE:FindByName(Name) -if not Zone then -Zone=AIRBASE:FindByName(Name):GetZone() -end -end -local Sound=Sound or"beacon.ogg" -if Zone then -if IsDropped then -local ZoneCoord=Zone -local ZoneVec3=ZoneCoord:GetVec3()or{x=0,y=0,z=0} -local Frequency=Mhz*1000000 -local Sound=self.RadioPath..Sound -trigger.action.radioTransmission(Sound,ZoneVec3,Modulation,false,Frequency,1000,Name..math.random(1,10000)) -self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = %d %d %d | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation)) -else -local ZoneCoord=Zone:GetCoordinate() -local ZoneVec3=ZoneCoord:GetVec3()or{x=0,y=0,z=0} -local Frequency=Mhz*1000000 -local Sound=self.RadioPath..Sound -trigger.action.radioTransmission(Sound,ZoneVec3,Modulation,false,Frequency,1000,Name..math.random(1,10000)) -self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = {x=%d, y=%d, z=%d} | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation)) -end -else -self:E(self.lid.."***** _AddRadioBeacon: Zone does not exist: "..Name) -end -return self -end -function CTLD:SetSoundfilesFolder(FolderPath) -self:T(self.lid.." SetSoundfilesFolder") -if FolderPath then -local lastchar=string.sub(FolderPath,-1) -if lastchar~="/"then -FolderPath=FolderPath.."/" -end -end -self.RadioPath=FolderPath -self:I(self.lid..string.format("Setting sound files folder to: %s",self.RadioPath)) -return self -end -function CTLD:_RefreshRadioBeacons() -self:T(self.lid.." _RefreshRadioBeacons") -local zones={[1]=self.pickupZones,[2]=self.wpZones,[3]=self.dropOffZones,[4]=self.shipZones,[5]=self.droppedBeacons} -for i=1,5 do -local IsShip=false -if i==4 then IsShip=true end -local IsDropped=false -if i==5 then IsDropped=true end -for index,cargozone in pairs(zones[i])do -local czone=cargozone -local Sound=self.RadioSound -local Silent=self.RadioSoundFC3 or self.RadioSound -if czone.active and czone.hasbeacon then -local FMbeacon=czone.fmbeacon -local VHFbeacon=czone.vhfbeacon -local UHFbeacon=czone.uhfbeacon -local Name=czone.name -local FM=FMbeacon.frequency -local VHF=VHFbeacon.frequency -local UHF=UHFbeacon.frequency -self:_AddRadioBeacon(Name,Sound,FM,CTLD.RadioModulation.FM,IsShip,IsDropped) -self:_AddRadioBeacon(Name,Sound,VHF,CTLD.RadioModulation.AM,IsShip,IsDropped) -self:_AddRadioBeacon(Name,Silent,UHF,CTLD.RadioModulation.AM,IsShip,IsDropped) -end -end -end -return self -end -function CTLD:IsUnitInZone(Unit,Zonetype) -self:T(self.lid.." IsUnitInZone") -self:T(Zonetype) -local unitname=Unit:GetName() -local zonetable={} -local outcome=false -if Zonetype==CTLD.CargoZoneType.LOAD then -zonetable=self.pickupZones -elseif Zonetype==CTLD.CargoZoneType.DROP then -zonetable=self.dropOffZones -elseif Zonetype==CTLD.CargoZoneType.SHIP then -zonetable=self.shipZones -else -zonetable=self.wpZones -end -local zonecoord=nil -local colorret=nil -local maxdist=1000000 -local zoneret=nil -local zonewret=nil -local zonenameret=nil -local unitcoord=Unit:GetCoordinate() -local unitVec2=unitcoord:GetVec2() -for _,_cargozone in pairs(zonetable)do -local czone=_cargozone -local zonename=czone.name -local active=czone.active -local color=czone.color -local zone=nil -local zoneradius=100 -local zonewidth=20 -if Zonetype==CTLD.CargoZoneType.SHIP then -self:T("Checking Type Ship: "..zonename) -local ZoneUNIT=UNIT:FindByName(zonename) -zonecoord=ZoneUNIT:GetCoordinate() -zoneradius=czone.shiplength -zonewidth=czone.shipwidth -zone=ZONE_UNIT:New(ZoneUNIT:GetName(),ZoneUNIT,zoneradius/2) -elseif ZONE:FindByName(zonename)then -zone=ZONE:FindByName(zonename) -self:T("Checking Zone: "..zonename) -zonecoord=zone:GetCoordinate() -zonewidth=zoneradius -elseif AIRBASE:FindByName(zonename)then -zone=AIRBASE:FindByName(zonename):GetZone() -self:T("Checking Zone: "..zonename) -zonecoord=zone:GetCoordinate() -zoneradius=2000 -zonewidth=zoneradius -end -local distance=self:_GetDistance(zonecoord,unitcoord) -if zone:IsVec2InZone(unitVec2)and active then -outcome=true -end -if maxdist>distance then -maxdist=distance -zoneret=zone -zonenameret=zonename -zonewret=zonewidth -colorret=color -end -end -if Zonetype==CTLD.CargoZoneType.SHIP then -return outcome,zonenameret,zoneret,maxdist,zonewret -else -return outcome,zonenameret,zoneret,maxdist -end -end -function CTLD:SmokePositionNow(Unit,Flare,SmokeColor) -self:T(self.lid.." SmokePositionNow") -local Smokecolor=self.SmokeColor or SMOKECOLOR.Red -if SmokeColor then -Smokecolor=SmokeColor -end -local FlareColor=self.FlareColor or FLARECOLOR.Red -local unitcoord=Unit:GetCoordinate() -local Group=Unit:GetGroup() -if Flare then -unitcoord:Flare(FlareColor,90) -else -local height=unitcoord:GetLandHeight()+2 -unitcoord.y=height -unitcoord:Smoke(Smokecolor) -end -return self -end -function CTLD:SmokeZoneNearBy(Unit,Flare) -self:T(self.lid.." SmokeZoneNearBy") -local unitcoord=Unit:GetCoordinate() -local Group=Unit:GetGroup() -local smokedistance=self.smokedistance -local smoked=false -local zones={[1]=self.pickupZones,[2]=self.wpZones,[3]=self.dropOffZones,[4]=self.shipZones} -for i=1,4 do -for index,cargozone in pairs(zones[i])do -local CZone=cargozone -local zonename=CZone.name -local zone=nil -if i==4 then -zone=UNIT:FindByName(zonename) -else -zone=ZONE:FindByName(zonename) -if not zone then -zone=AIRBASE:FindByName(zonename):GetZone() -end -end -local zonecoord=zone:GetCoordinate() -local active=CZone.active -local color=CZone.color -local distance=self:_GetDistance(zonecoord,unitcoord) -if distance=minh)then -outcome=true -end -end -return outcome -end -function CTLD:IsCorrectFlightParameters(Unit) -self:T(self.lid.." IsCorrectFlightParameters") -local outcome=false -if self:IsUnitInAir(Unit)then -local uspeed=Unit:GetVelocityMPS() -local uheight=Unit:GetHeight() -local ucoord=Unit:GetCoordinate() -if not ucoord then -return false -end -local gheight=ucoord:GetLandHeight() -local aheight=uheight-gheight -local minh=self.HercMinAngels -local maxh=self.HercMaxAngels -local maxspeed=self.HercMaxSpeed -local kmspeed=uspeed*3.6 -local knspeed=kmspeed/1.86 -self:T(string.format("%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn",self.lid,aheight,uspeed,kmspeed,knspeed)) -if(aheight<=maxh)and(aheight>=minh)and(uspeed<=maxspeed)then -outcome=true -end -end -return outcome -end -function CTLD:_ShowHoverParams(Group,Unit) -local inhover=self:IsCorrectHover(Unit) -local htxt="true" -if not inhover then htxt="false"end -local text="" -if _SETTINGS:IsMetric()then -text=string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s",self.minimumHoverHeight,self.maximumHoverHeight,htxt) -else -local minheight=UTILS.MetersToFeet(self.minimumHoverHeight) -local maxheight=UTILS.MetersToFeet(self.maximumHoverHeight) -text=string.format("Hover parameters (autoload/drop):\n - Min height %dft \n - Max height %dft \n - Max speed 6ftps \n - In parameter: %s",minheight,maxheight,htxt) -end -self:_SendMessage(text,10,false,Group) -return self -end -function CTLD:_ShowFlightParams(Group,Unit) -local inhover=self:IsCorrectFlightParameters(Unit) -local htxt="true" -if not inhover then htxt="false"end -local text="" -if _SETTINGS:IsImperial()then -local minheight=UTILS.MetersToFeet(self.HercMinAngels) -local maxheight=UTILS.MetersToFeet(self.HercMaxAngels) -text=string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s",minheight,maxheight,htxt) -else -local minheight=self.HercMinAngels -local maxheight=self.HercMaxAngels -text=string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s",minheight,maxheight,htxt) -end -self:_SendMessage(text,10,false,Group) -return self -end -function CTLD:CanHoverLoad(Unit) -self:T(self.lid.." CanHoverLoad") -if self:IsHercules(Unit)then return false end -local outcome=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)and self:IsCorrectHover(Unit) -if not outcome then -outcome=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) -end -return outcome -end -function CTLD:IsUnitInAir(Unit) -local minheight=self.minimumHoverHeight -if self.enableHercules and self:IsHercules(Unit)then -minheight=5.1 -end -local uheight=Unit:GetHeight() -local ucoord=Unit:GetCoordinate() -if not ucoord then -return false -end -local gheight=ucoord:GetLandHeight() -local aheight=uheight-gheight -if aheight>=minheight then -return true -else -return false -end -end -function CTLD:AutoHoverLoad(Unit) -self:T(self.lid.." AutoHoverLoad") -local unittype=Unit:GetTypeName() -local unitname=Unit:GetName() -local Group=Unit:GetGroup() -local capabilities=self:_GetUnitCapabilities(Unit) -local cancrates=capabilities.crates -local cratelimit=capabilities.cratelimit -if cancrates then -local numberonboard=0 -local loaded={} -if self.Loaded_Cargo[unitname]then -loaded=self.Loaded_Cargo[unitname] -numberonboard=loaded.Cratesloaded or 0 -end -local load=cratelimit-numberonboard -local canload=self:CanHoverLoad(Unit) -if canload and load>0 then -self:_LoadCratesNearby(Group,Unit) -end -end -return self -end -function CTLD:CheckAutoHoverload() -if self.hoverautoloading then -for _,_pilot in pairs(self.CtldUnits)do -local Unit=UNIT:FindByName(_pilot) -if self:CanHoverLoad(Unit)then self:AutoHoverLoad(Unit)end -end -end -return self -end -function CTLD:CleanDroppedTroops() -local troops=self.DroppedTroops -local newtable={} -for _index,_group in pairs(troops)do -self:T({_group.ClassName}) -if _group and _group.ClassName=="GROUP"then -if _group:IsAlive()then -newtable[_index]=_group -end -end -end -self.DroppedTroops=newtable -local engineers=self.EngineersInField -local engtable={} -for _index,_group in pairs(engineers)do -self:T({_group.ClassName}) -if _group and _group:IsNotStatus("Stopped")then -engtable[_index]=_group -end -end -self.EngineersInField=engtable -return self -end -function CTLD:AddStockTroops(Name,Number) -local name=Name or"none" -local number=Number or 1 -local gentroops=self.Cargo_Troops -for _id,_troop in pairs(gentroops)do -if _troop.Name==name then -_troop:AddStock(number) -end -end -return self -end -function CTLD:AddStockCrates(Name,Number) -local name=Name or"none" -local number=Number or 1 -local gentroops=self.Cargo_Crates -for _id,_troop in pairs(gentroops)do -if _troop.Name==name then -_troop:AddStock(number) -end -end -return self -end -function CTLD:AddStockStatics(Name,Number) -local name=Name or"none" -local number=Number or 1 -local gentroops=self.Cargo_Statics -for _id,_troop in pairs(gentroops)do -if _troop.Name==name then -_troop:AddStock(number) -end -end -return self -end -function CTLD:SetStockCrates(Name,Number) -local name=Name or"none" -local number=Number -local gentroops=self.Cargo_Crates -for _id,_troop in pairs(gentroops)do -if _troop.Name==name then -_troop:SetStock(number) -end -end -return self -end -function CTLD:SetStockTroops(Name,Number) -local name=Name or"none" -local number=Number -local gentroops=self.Cargo_Troops -for _id,_troop in pairs(gentroops)do -if _troop.Name==name then -_troop:SetStock(number) -end -end -return self -end -function CTLD:SetStockStatics(Name,Number) -local name=Name or"none" -local number=Number -local gentroops=self.Cargo_Statics -for _id,_troop in pairs(gentroops)do -if _troop.Name==name then -_troop:SetStock(number) -end -end -return self -end -function CTLD:GetStockCrates() -local Stock={} -local gentroops=self.Cargo_Crates -for _id,_troop in pairs(gentroops)do -table.insert(Stock,_troop.Name,_troop.Stock or-1) -end -return Stock -end -function CTLD:GetStockTroops() -local Stock={} -local gentroops=self.Cargo_Troops -for _id,_troop in pairs(gentroops)do -table.insert(Stock,_troop.Name,_troop.Stock or-1) -end -return Stock -end -function CTLD:GetStockStatics() -local Stock={} -local gentroops=self.Cargo_Statics -for _id,_troop in pairs(gentroops)do -table.insert(Stock,_troop.Name,_troop.Stock or-1) -end -return Stock -end -function CTLD:RemoveStockTroops(Name,Number) -local name=Name or"none" -local number=Number or 1 -local gentroops=self.Cargo_Troops -for _id,_troop in pairs(gentroops)do -if _troop.Name==name then -_troop:RemoveStock(number) -end -end -return self -end -function CTLD:RemoveStockCrates(Name,Number) -local name=Name or"none" -local number=Number or 1 -local gentroops=self.Cargo_Crates -for _id,_troop in pairs(gentroops)do -if _troop.Name==name then -_troop:RemoveStock(number) -end -end -return self -end -function CTLD:RemoveStockStatics(Name,Number) -local name=Name or"none" -local number=Number or 1 -local gentroops=self.Cargo_Statics -for _id,_troop in pairs(gentroops)do -if _troop.Name==name then -_troop:RemoveStock(number) -end -end -return self -end -function CTLD:_CheckEngineers() -self:T(self.lid.." CheckEngineers") -local engtable=self.EngineersInField -for _ind,_engineers in pairs(engtable)do -local engineers=_engineers -local wrenches=engineers.Group -self:T(_engineers.lid.._engineers:GetStatus()) -if wrenches and wrenches:IsAlive()then -if engineers:IsStatus("Running")or engineers:IsStatus("Searching")then -local crates,number=self:_FindCratesNearby(wrenches,nil,self.EngineerSearch,true) -engineers:Search(crates,number) -elseif engineers:IsStatus("Moving")then -engineers:Move() -elseif engineers:IsStatus("Arrived")then -engineers:Build() -local unit=wrenches:GetUnit(1) -self:_BuildCrates(wrenches,unit,true) -self:_RepairCrates(wrenches,unit,true) -engineers:Done() -end -else -engineers:Stop() -end -end -return self -end -function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation,Structure) -self:T(self.lid.." InjectTroops") -local cargo=Cargo -local function IsTroopsMatch(cargo) -local match=false -local cgotbl=self.Cargo_Troops -local name=cargo:GetName() -for _,_cgo in pairs(cgotbl)do -local cname=_cgo:GetName() -if name==cname then -match=true -break -end -end -return match -end -local function Cruncher(group,typename,anzahl) -local units=group:GetUnits() -local reduced=0 -for _,_unit in pairs(units)do -local typo=_unit:GetTypeName() -if typename==typo then -_unit:Destroy(false) -reduced=reduced+1 -if reduced==anzahl then break end -end -end -end -local function PostSpawn(args) -local group=args[1] -local structure=args[2] -if structure then -local loadedstructure={} -local strcset=UTILS.Split(structure,";") -for _,_data in pairs(strcset)do -local datasplit=UTILS.Split(_data,"==") -loadedstructure[datasplit[1]]=tonumber(datasplit[2]) -end -local originalstructure=UTILS.GetCountPerTypeName(group) -for _name,_number in pairs(originalstructure)do -local loadednumber=0 -if loadedstructure[_name]then -loadednumber=loadedstructure[_name] -end -local reduce=false -if loadednumber<_number then reduce=true end -if reduce then -Cruncher(group,_name,_number-loadednumber) -end -end -end -end -if not IsTroopsMatch(cargo)then -self.CargoCounter=self.CargoCounter+1 -cargo.ID=self.CargoCounter -cargo.Stock=1 -table.insert(self.Cargo_Troops,cargo) -end -local type=cargo:GetType() -if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then -local name=cargo:GetName()or"none" -local temptable=cargo:GetTemplates()or{} -local factor=1.5 -local zone=Zone -local randomcoord=zone:GetRandomCoordinate(10,30*factor,Surfacetypes):GetVec2() -if PreciseLocation then -randomcoord=zone:GetCoordinate():GetVec2() -end -for _,_template in pairs(temptable)do -self.TroopCounter=self.TroopCounter+1 -local alias=string.format("%s-%d",_template,math.random(1,100000)) -self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) -:InitRandomizeUnits(true,20,2) -:InitDelayOff() -:SpawnFromVec2(randomcoord) -if self.movetroopstowpzone and type~=CTLD_CARGO.Enum.ENGINEERS then -self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) -end -end -cargo:SetWasDropped(true) -if type==CTLD_CARGO.Enum.ENGINEERS then -self.Engineers=self.Engineers+1 -local grpname=self.DroppedTroops[self.TroopCounter]:GetName() -self.EngineersInField[self.Engineers]=CTLD_ENGINEERING:New(name,grpname) -end -if Structure then -BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure}) -end -if self.eventoninject then -self:__TroopsDeployed(1,nil,nil,self.DroppedTroops[self.TroopCounter],type) -end -end -return self -end -function CTLD:InjectVehicles(Zone,Cargo,Surfacetypes,PreciseLocation,Structure) -self:T(self.lid.." InjectVehicles") -local cargo=Cargo -local function IsVehicMatch(cargo) -local match=false -local cgotbl=self.Cargo_Crates -local name=cargo:GetName() -for _,_cgo in pairs(cgotbl)do -local cname=_cgo:GetName() -if name==cname then -match=true -break -end -end -return match -end -local function Cruncher(group,typename,anzahl) -local units=group:GetUnits() -local reduced=0 -for _,_unit in pairs(units)do -local typo=_unit:GetTypeName() -if typename==typo then -_unit:Destroy(false) -reduced=reduced+1 -if reduced==anzahl then break end -end -end -end -local function PostSpawn(args) -local group=args[1] -local structure=args[2] -if structure then -local loadedstructure={} -local strcset=UTILS.Split(structure,";") -for _,_data in pairs(strcset)do -local datasplit=UTILS.Split(_data,"==") -loadedstructure[datasplit[1]]=tonumber(datasplit[2]) -end -local originalstructure=UTILS.GetCountPerTypeName(group) -for _name,_number in pairs(originalstructure)do -local loadednumber=0 -if loadedstructure[_name]then -loadednumber=loadedstructure[_name] -end -local reduce=false -if loadednumber<_number then reduce=true end -if reduce then -Cruncher(group,_name,_number-loadednumber) -end -end -end -end -if not IsVehicMatch(cargo)then -self.CargoCounter=self.CargoCounter+1 -cargo.ID=self.CargoCounter -cargo.Stock=1 -table.insert(self.Cargo_Crates,cargo) -end -local type=cargo:GetType() -if(type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then -local name=cargo:GetName()or"none" -local temptable=cargo:GetTemplates()or{} -local factor=1.5 -local zone=Zone -local randomcoord=zone:GetRandomCoordinate(10,30*factor,Surfacetypes):GetVec2() -if PreciseLocation then -randomcoord=zone:GetCoordinate():GetVec2() -end -cargo:SetWasDropped(true) -local canmove=false -if type==CTLD_CARGO.Enum.VEHICLE then canmove=true end -for _,_template in pairs(temptable)do -self.TroopCounter=self.TroopCounter+1 -local alias=string.format("%s-%d",_template,math.random(1,100000)) -if canmove then -self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) -:InitRandomizeUnits(true,20,2) -:InitDelayOff() -:SpawnFromVec2(randomcoord) -else -self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) -:InitDelayOff() -:SpawnFromVec2(randomcoord) -end -if Structure then -BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure}) -end -if self.eventoninject then -self:__CratesBuild(1,nil,nil,self.DroppedTroops[self.TroopCounter]) -end -end -end -return self -end -function CTLD:onafterStart(From,Event,To) -self:T({From,Event,To}) -self:I(self.lid.."Started ("..self.version..")") -if self.useprefix or self.enableHercules then -local prefix=self.prefixes -if self.enableHercules then -self.PilotGroups=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() -else -self.PilotGroups=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() -end -else -self.PilotGroups=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategories("helicopter"):FilterStart() -end -self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) -self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler) -self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler) -self:__Status(-5) -if self.enableLoadSave then -local interval=self.saveinterval -local filename=self.filename -local filepath=self.filepath -self:__Save(interval,filepath,filename) -end -return self -end -function CTLD:onbeforeStatus(From,Event,To) -self:T({From,Event,To}) -self:CleanDroppedTroops() -self:_RefreshF10Menus() -self:CheckDroppedBeacons() -self:_RefreshRadioBeacons() -self:CheckAutoHoverload() -self:_CheckEngineers() -return self -end -function CTLD:onafterStatus(From,Event,To) -self:T({From,Event,To}) -local pilots=0 -for _,_pilot in pairs(self.CtldUnits)do -pilots=pilots+1 -end -local boxes=0 -for _,_pilot in pairs(self.Spawned_Cargo)do -boxes=boxes+1 -end -local cc=self.CargoCounter -local tc=self.TroopCounter -if self.debug or self.verbose>0 then -local text=string.format("%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d",self.lid,pilots,boxes,cc,tc) -local m=MESSAGE:New(text,10,"CTLD"):ToAll() -if self.verbose>0 then -self:I(self.lid.."Cargo and Troops in Stock:") -for _,_troop in pairs(self.Cargo_Crates)do -local name=_troop:GetName() -local stock=_troop:GetStock() -self:I(string.format("-- %s \t\t\t %d",name,stock)) -end -for _,_troop in pairs(self.Cargo_Statics)do -local name=_troop:GetName() -local stock=_troop:GetStock() -self:I(string.format("-- %s \t\t\t %d",name,stock)) -end -for _,_troop in pairs(self.Cargo_Troops)do -local name=_troop:GetName() -local stock=_troop:GetStock() -self:I(string.format("-- %s \t\t %d",name,stock)) -end -end -end -self:__Status(-30) -return self -end -function CTLD:onafterStop(From,Event,To) -self:T({From,Event,To}) -self:UnhandleEvent(EVENTS.PlayerEnterAircraft) -self:UnhandleEvent(EVENTS.PlayerEnterUnit) -self:UnhandleEvent(EVENTS.PlayerLeaveUnit) -return self -end -function CTLD:onbeforeTroopsPickedUp(From,Event,To,Group,Unit,Cargo) -self:T({From,Event,To}) -return self -end -function CTLD:onbeforeCratesPickedUp(From,Event,To,Group,Unit,Cargo) -self:T({From,Event,To}) -return self -end -function CTLD:onbeforeTroopsExtracted(From,Event,To,Group,Unit,Troops) -self:T({From,Event,To}) -return self -end -function CTLD:onbeforeTroopsDeployed(From,Event,To,Group,Unit,Troops) -self:T({From,Event,To}) -if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then -local playername=Unit:GetPlayerName() -local dropcoord=Troops:GetCoordinate()or COORDINATE:New(0,0,0) -local dropvec2=dropcoord:GetVec2() -self.PlayerTaskQueue:ForEach( -function(Task) -local task=Task -local subtype=task:GetSubType() -if Event==subtype and not task:IsDone()then -local targetzone=task.Target:GetObject() -if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and targetzone:IsVec2InZone(dropvec2)then -if task.Clients:HasUniqueID(playername)then -task:__Success(-1) -end -end -end -end -) -end -return self -end -function CTLD:onafterTroopsDeployed(From,Event,To,Group,Unit,Troops,Type) -self:T({From,Event,To}) -if self.movetroopstowpzone and Type~=CTLD_CARGO.Enum.ENGINEERS then -self:_MoveGroupToZone(Troops) -end -return self -end -function CTLD:onbeforeCratesDropped(From,Event,To,Group,Unit,Cargotable) -self:T({From,Event,To}) -return self -end -function CTLD:onbeforeCratesBuild(From,Event,To,Group,Unit,Vehicle) -self:T({From,Event,To}) -if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then -local playername=Unit:GetPlayerName() -local dropcoord=Vehicle:GetCoordinate()or COORDINATE:New(0,0,0) -local dropvec2=dropcoord:GetVec2() -self.PlayerTaskQueue:ForEach( -function(Task) -local task=Task -local subtype=task:GetSubType() -if Event==subtype and not task:IsDone()then -local targetzone=task.Target:GetObject() -if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and targetzone:IsVec2InZone(dropvec2)then -if task.Clients:HasUniqueID(playername)then -task:__Success(-1) -end -end -end -end -) -end -return self -end -function CTLD:onafterCratesBuild(From,Event,To,Group,Unit,Vehicle) -self:T({From,Event,To}) -if self.movetroopstowpzone then -self:_MoveGroupToZone(Vehicle) -end -return self -end -function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit) -self:T({From,Event,To}) -return self -end -function CTLD:onbeforeSave(From,Event,To,path,filename) -self:T({From,Event,To,path,filename}) -if not self.enableLoadSave then -return self -end -if not io then -self:E(self.lid.."ERROR: io not desanitized. Can't save current state.") -return false -end -if path==nil and not lfs then -self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") -end -return true -end -function CTLD:onafterSave(From,Event,To,path,filename) -self:T({From,Event,To,path,filename}) -if not self.enableLoadSave then -return self -end -local function _savefile(filename,data) -local f=assert(io.open(filename,"wb")) -f:write(data) -f:close() -end -if lfs then -path=self.filepath or lfs.writedir() -end -filename=filename or self.filename -if path~=nil then -filename=path.."\\"..filename -end -local grouptable=self.DroppedTroops -local cgovehic=self.Cargo_Crates -local cgotable=self.Cargo_Troops -local stcstable=self.Spawned_Cargo -local statics=nil -local statics={} -self:T(self.lid.."Bulding Statics Table for Saving") -for _,_cargo in pairs(stcstable)do -local cargo=_cargo -local object=cargo:GetPositionable() -if object and object:IsAlive()and(cargo:WasDropped()or not cargo:HasMoved())then -statics[#statics+1]=cargo -end -end -local function FindCargoType(name,table) -local match=false -local cargo=nil -name=string.gsub(name,"-"," ") -for _ind,_cargo in pairs(table)do -local thiscargo=_cargo -local template=thiscargo:GetTemplates() -if type(template)=="string"then -template={template} -end -for _,_name in pairs(template)do -_name=string.gsub(_name,"-"," ") -if string.find(name,_name)and _cargo:GetType()~=CTLD_CARGO.Enum.REPAIR then -match=true -cargo=thiscargo -end -end -if match then break end -end -return match,cargo -end -local data="Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass,Structure\n" -local n=0 -for _,_grp in pairs(grouptable)do -local group=_grp -if group and group:IsAlive()then -local name=group:GetName() -local template=name -if string.find(template,"#")then -template=string.gsub(name,"#(%d+)$","") -end -local template=string.gsub(name,"-(%d+)$","") -local match,cargo=FindCargoType(template,cgotable) -if not match then -match,cargo=FindCargoType(template,cgovehic) -end -if match then -n=n+1 -local cargo=cargo -local cgoname=cargo.Name -local cgotemp=cargo.Templates -local cgotype=cargo.CargoType -local cgoneed=cargo.CratesNeeded -local cgomass=cargo.PerCrateMass -local structure=UTILS.GetCountPerTypeName(group) -local strucdata="" -for typen,anzahl in pairs(structure)do -strucdata=strucdata..typen.."=="..anzahl..";" -end -if type(cgotemp)=="table"then -local templates="{" -for _,_tmpl in pairs(cgotemp)do -templates=templates.._tmpl..";" -end -templates=templates.."}" -cgotemp=templates -end -local location=group:GetVec3() -local txt=string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,%s\n" -,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,strucdata) -data=data..txt -end -end -end -for _,_cgo in pairs(statics)do -local object=_cgo -local cgoname=object.Name -local cgotemp=object.Templates -if type(cgotemp)=="table"then -local templates="{" -for _,_tmpl in pairs(cgotemp)do -templates=templates.._tmpl..";" -end -templates=templates.."}" -cgotemp=templates -end -local cgotype=object.CargoType -local cgoneed=object.CratesNeeded -local cgomass=object.PerCrateMass -local crateobj=object.Positionable -local location=crateobj:GetVec3() -local txt=string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n" -,"STATIC",location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass) -data=data..txt -end -_savefile(filename,data) -if self.enableLoadSave then -local interval=self.saveinterval -local filename=self.filename -local filepath=self.filepath -self:__Save(interval,filepath,filename) -end -return self -end -function CTLD:onbeforeLoad(From,Event,To,path,filename) -self:T({From,Event,To,path,filename}) -if not self.enableLoadSave then -return self -end -local function _fileexists(name) -local f=io.open(name,"r") -if f~=nil then -io.close(f) -return true -else -return false -end -end -filename=filename or self.filename -path=path or self.filepath -if not io then -self:E(self.lid.."WARNING: io not desanitized. Cannot load file.") -return false -end -if path==nil and not lfs then -self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") -end -if lfs then -path=path or lfs.writedir() -end -if path~=nil then -filename=path.."\\"..filename -end -local exists=_fileexists(filename) -if exists then -return true -else -self:E(self.lid..string.format("WARNING: State file %s might not exist.",filename)) -return false -end -end -function CTLD:onafterLoad(From,Event,To,path,filename) -self:T({From,Event,To,path,filename}) -if not self.enableLoadSave then -return self -end -local function _loadfile(filename) -local f=assert(io.open(filename,"rb")) -local data=f:read("*all") -f:close() -return data -end -filename=filename or self.filename -path=path or self.filepath -if lfs then -path=path or lfs.writedir() -end -if path~=nil then -filename=path.."\\"..filename -end -local text=string.format("Loading CTLD state from file %s",filename) -MESSAGE:New(text,10):ToAllIf(self.Debug) -self:I(self.lid..text) -local file=assert(io.open(filename,"rb")) -local loadeddata={} -for line in file:lines()do -loadeddata[#loadeddata+1]=line -end -file:close() -table.remove(loadeddata,1) -for _id,_entry in pairs(loadeddata)do -local dataset=UTILS.Split(_entry,",") -local groupname=dataset[1] -local vec2={} -vec2.x=tonumber(dataset[2]) -vec2.y=tonumber(dataset[4]) -local cargoname=dataset[5] -local cargotype=dataset[7] -if type(groupname)=="string"and groupname~="STATIC"then -local cargotemplates=dataset[6] -cargotemplates=string.gsub(cargotemplates,"{","") -cargotemplates=string.gsub(cargotemplates,"}","") -cargotemplates=UTILS.Split(cargotemplates,";") -local size=tonumber(dataset[8]) -local mass=tonumber(dataset[9]) -local structure=nil -if dataset[10]then -structure=dataset[10] -structure=string.gsub(structure,",","") -end -local dropzone=ZONE_RADIUS:New("DropZone",vec2,20) -if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then -local injectvehicle=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) -self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure) -elseif cargotype==CTLD_CARGO.Enum.TROOPS or cargotype==CTLD_CARGO.Enum.ENGINEERS then -local injecttroops=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) -self:InjectTroops(dropzone,injecttroops,self.surfacetypes,self.useprecisecoordloads,structure) -end -elseif(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then -local cargotemplates=dataset[6] -local size=tonumber(dataset[8]) -local mass=tonumber(dataset[9]) -local dropzone=ZONE_RADIUS:New("DropZone",vec2,20) -local injectstatic=nil -if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then -cargotemplates=string.gsub(cargotemplates,"{","") -cargotemplates=string.gsub(cargotemplates,"}","") -cargotemplates=UTILS.Split(cargotemplates,";") -injectstatic=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) -elseif cargotype==CTLD_CARGO.Enum.STATIC or cargotype==CTLD_CARGO.Enum.REPAIR then -injectstatic=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) -end -if injectstatic then -self:InjectStatics(dropzone,injectstatic) -end -end -end -return self -end -end -do -CTLD_HERCULES={ -ClassName="CTLD_HERCULES", -lid="", -Name="", -Version="0.0.3", -} -CTLD_HERCULES.Types={ -["ATGM M1045 HMMWV TOW Air [7183lb]"]={['name']="M1045 HMMWV TOW",['container']=true}, -["ATGM M1045 HMMWV TOW Skid [7073lb]"]={['name']="M1045 HMMWV TOW",['container']=false}, -["APC M1043 HMMWV Armament Air [7023lb]"]={['name']="M1043 HMMWV Armament",['container']=true}, -["APC M1043 HMMWV Armament Skid [6912lb]"]={['name']="M1043 HMMWV Armament",['container']=false}, -["SAM Avenger M1097 Air [7200lb]"]={['name']="M1097 Avenger",['container']=true}, -["SAM Avenger M1097 Skid [7090lb]"]={['name']="M1097 Avenger",['container']=false}, -["APC Cobra Air [10912lb]"]={['name']="Cobra",['container']=true}, -["APC Cobra Skid [10802lb]"]={['name']="Cobra",['container']=false}, -["APC M113 Air [21624lb]"]={['name']="M-113",['container']=true}, -["APC M113 Skid [21494lb]"]={['name']="M-113",['container']=false}, -["Tanker M978 HEMTT [34000lb]"]={['name']="M978 HEMTT Tanker",['container']=false}, -["HEMTT TFFT [34400lb]"]={['name']="HEMTT TFFT",['container']=false}, -["SPG M1128 Stryker MGS [33036lb]"]={['name']="M1128 Stryker MGS",['container']=false}, -["AAA Vulcan M163 Air [21666lb]"]={['name']="Vulcan",['container']=true}, -["AAA Vulcan M163 Skid [21577lb]"]={['name']="Vulcan",['container']=false}, -["APC M1126 Stryker ICV [29542lb]"]={['name']="M1126 Stryker ICV",['container']=false}, -["ATGM M1134 Stryker [30337lb]"]={['name']="M1134 Stryker ATGM",['container']=false}, -["APC LAV-25 Air [22520lb]"]={['name']="LAV-25",['container']=true}, -["APC LAV-25 Skid [22514lb]"]={['name']="LAV-25",['container']=false}, -["M1025 HMMWV Air [6160lb]"]={['name']="Hummer",['container']=true}, -["M1025 HMMWV Skid [6050lb]"]={['name']="Hummer",['container']=false}, -["IFV M2A2 Bradley [34720lb]"]={['name']="M-2 Bradley",['container']=false}, -["IFV MCV-80 [34720lb]"]={['name']="MCV-80",['container']=false}, -["IFV BMP-1 [23232lb]"]={['name']="BMP-1",['container']=false}, -["IFV BMP-2 [25168lb]"]={['name']="BMP-2",['container']=false}, -["IFV BMP-3 [32912lb]"]={['name']="BMP-3",['container']=false}, -["ARV BRDM-2 Air [12320lb]"]={['name']="BRDM-2",['container']=true}, -["ARV BRDM-2 Skid [12210lb]"]={['name']="BRDM-2",['container']=false}, -["APC BTR-80 Air [23936lb]"]={['name']="BTR-80",['container']=true}, -["APC BTR-80 Skid [23826lb]"]={['name']="BTR-80",['container']=false}, -["APC BTR-82A Air [24998lb]"]={['name']="BTR-82A",['container']=true}, -["APC BTR-82A Skid [24888lb]"]={['name']="BTR-82A",['container']=false}, -["SAM ROLAND ADS [34720lb]"]={['name']="Roland Radar",['container']=false}, -["SAM ROLAND LN [34720b]"]={['name']="Roland ADS",['container']=false}, -["SAM SA-13 STRELA [21624lb]"]={['name']="Strela-10M3",['container']=false}, -["AAA ZSU-23-4 Shilka [32912lb]"]={['name']="ZSU-23-4 Shilka",['container']=false}, -["SAM SA-19 Tunguska 2S6 [34720lb]"]={['name']="2S6 Tunguska",['container']=false}, -["Transport UAZ-469 Air [3747lb]"]={['name']="UAZ-469",['container']=true}, -["Transport UAZ-469 Skid [3630lb]"]={['name']="UAZ-469",['container']=false}, -["AAA GEPARD [34720lb]"]={['name']="Gepard",['container']=false}, -["SAM CHAPARRAL Air [21624lb]"]={['name']="M48 Chaparral",['container']=true}, -["SAM CHAPARRAL Skid [21516lb]"]={['name']="M48 Chaparral",['container']=false}, -["SAM LINEBACKER [34720lb]"]={['name']="M6 Linebacker",['container']=false}, -["Transport URAL-375 [14815lb]"]={['name']="Ural-375",['container']=false}, -["Transport M818 [16000lb]"]={['name']="M 818",['container']=false}, -["IFV MARDER [34720lb]"]={['name']="Marder",['container']=false}, -["Transport Tigr Air [15900lb]"]={['name']="Tigr_233036",['container']=true}, -["Transport Tigr Skid [15730lb]"]={['name']="Tigr_233036",['container']=false}, -["IFV TPZ FUCH [33440lb]"]={['name']="TPZ",['container']=false}, -["IFV BMD-1 Air [18040lb]"]={['name']="BMD-1",['container']=true}, -["IFV BMD-1 Skid [17930lb]"]={['name']="BMD-1",['container']=false}, -["IFV BTR-D Air [18040lb]"]={['name']="BTR_D",['container']=true}, -["IFV BTR-D Skid [17930lb]"]={['name']="BTR_D",['container']=false}, -["EWR SBORKA Air [21624lb]"]={['name']="Dog Ear radar",['container']=true}, -["EWR SBORKA Skid [21624lb]"]={['name']="Dog Ear radar",['container']=false}, -["ART 2S9 NONA Air [19140lb]"]={['name']="SAU 2-C9",['container']=true}, -["ART 2S9 NONA Skid [19030lb]"]={['name']="SAU 2-C9",['container']=false}, -["ART GVOZDIKA [34720lb]"]={['name']="SAU Gvozdika",['container']=false}, -["APC MTLB Air [26400lb]"]={['name']="MTLB",['container']=true}, -["APC MTLB Skid [26290lb]"]={['name']="MTLB",['container']=false}, -} -function CTLD_HERCULES:New(Coalition,Alias,CtldObject) -local self=BASE:Inherit(self,FSM:New()) -if Coalition and type(Coalition)=="string"then -if Coalition=="blue"then -self.coalition=coalition.side.BLUE -self.coalitiontxt=Coalition -elseif Coalition=="red"then -self.coalition=coalition.side.RED -self.coalitiontxt=Coalition -elseif Coalition=="neutral"then -self.coalition=coalition.side.NEUTRAL -self.coalitiontxt=Coalition -else -self:E("ERROR: Unknown coalition in CTLD!") -end -else -self.coalition=Coalition -self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition)) -end -if Alias then -self.alias=tostring(Alias) -else -self.alias="UNHCR" -if self.coalition then -if self.coalition==coalition.side.RED then -self.alias="Red CTLD Hercules" -elseif self.coalition==coalition.side.BLUE then -self.alias="Blue CTLD Hercules" -end -end -end -self.lid=string.format("%s (%s) | ",self.alias,self.coalitiontxt) -self.infantrytemplate="Infantry" -self.CTLD=CtldObject -self.verbose=true -self.j=0 -self.carrierGroups={} -self.Cargo={} -self.ParatrooperCount={} -self.ObjectTracker={} -self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") -self:HandleEvent(EVENTS.Shot,self._HandleShot) -self:I(self.lid.."Started") -self:CheckTemplates() -return self -end -function CTLD_HERCULES:CheckTemplates() -self:T(self.lid..'CheckTemplates') -self.Types["Paratroopers 10"]={ -name=self.infantrytemplate, -container=false, -available=false, -} -local missing={} -local nomissing=0 -local found={} -local nofound=0 -for _index,_tab in pairs(self.Types)do -local outcometxt="MISSING" -if _DATABASE.Templates.Groups[_tab.name]then -outcometxt="OK" -self.Types[_index].available=true -found[_tab.name]=true -else -self.Types[_index].available=false -missing[_tab.name]=true -end -if self.verbose then -self:I(string.format(self.lid.."Checking template for %s (%s) ... %s",_index,_tab.name,outcometxt)) -end -end -for _,_name in pairs(found)do -nofound=nofound+1 -end -for _,_name in pairs(missing)do -nomissing=nomissing+1 -end -self:I(string.format(self.lid.."Template Check Summary: Found %d, Missing %d, Total %d",nofound,nomissing,nofound+nomissing)) -return self -end -function CTLD_HERCULES:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position,Cargo_Type_name,CargoHeading,Cargo_Country,GroupSpacing) -self:T(self.lid..'Soldier_SpawnGroup') -self:T(Cargo_Drop_Position) -local InjectTroopsType=CTLD_CARGO:New(nil,self.infantrytemplate,{self.infantrytemplate},CTLD_CARGO.Enum.TROOPS,true,true,10,nil,false,80) -local position=Cargo_Drop_Position:GetVec2() -local dropzone=ZONE_RADIUS:New("Infantry "..math.random(1,10000),position,100) -self.CTLD:InjectTroops(dropzone,InjectTroopsType) -return self -end -function CTLD_HERCULES:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position,Cargo_Type_name,CargoHeading,Cargo_Country) -self:T(self.lid.."Cargo_SpawnGroup") -self:T(Cargo_Type_name) -if Cargo_Type_name~='Container red 1'then -local InjectVehicleType=CTLD_CARGO:New(nil,Cargo_Type_name,{Cargo_Type_name},CTLD_CARGO.Enum.VEHICLE,true,true,1,nil,false,1000) -local position=Cargo_Drop_Position:GetVec2() -local dropzone=ZONE_RADIUS:New("Vehicle "..math.random(1,10000),position,100) -self.CTLD:InjectVehicles(dropzone,InjectVehicleType) -end -return self -end -function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Position,Cargo_Type_name,CargoHeading,dead,Cargo_Country) -self:T(self.lid.."Cargo_SpawnStatic") -self:T("Static "..Cargo_Type_name.." Dead "..tostring(dead)) -local position=Cargo_Drop_Position:GetVec2() -local Zone=ZONE_RADIUS:New("Cargo Static "..math.random(1,10000),position,100) -if not dead then -local injectstatic=CTLD_CARGO:New(nil,"Cargo Static Group "..math.random(1,10000),"iso_container",CTLD_CARGO.Enum.STATIC,true,false,1,nil,true,4500,1) -self.CTLD:InjectStatics(Zone,injectstatic,true) -end -return self -end -function CTLD_HERCULES:Cargo_SpawnDroppedAsCargo(_name,_pos) -local theCargo=self.CTLD:_FindCratesCargoObject(_name) -if theCargo then -self.CTLD.CrateCounter=self.CTLD.CrateCounter+1 -self.CTLD.CargoCounter=self.CTLD.CargoCounter+1 -local basetype=self.CTLD.basetype or"container_cargo" -local theStatic=SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) -:InitCargoMass(theCargo.PerCrateMass) -:InitCargo(self.CTLD.enableslingload) -:InitCoordinate(_pos) -:Spawn(270,_name.."-Container-"..math.random(1,100000)) -self.CTLD.Spawned_Crates[self.CTLD.CrateCounter]=theStatic -local newCargo=CTLD_CARGO:New(self.CTLD.CargoCounter,theCargo.Name,theCargo.Templates,theCargo.CargoType,true,false,theCargo.CratesNeeded,self.CTLD.Spawned_Crates[self.CTLD.CrateCounter],true,theCargo.PerCrateMass,nil,theCargo.Subcategory) -table.insert(self.CTLD.Spawned_Cargo,newCargo) -newCargo:SetWasDropped(true) -newCargo:SetHasMoved(true) -end -return self -end -function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direction,Cargo_Content_position,Cargo_Type_name,Cargo_over_water,Container_Enclosed,ParatrooperGroupSpawn,offload_cargo,all_cargo_survive_to_the_ground,all_cargo_gets_destroyed,destroy_cargo_dropped_without_parachute,Cargo_Country) -self:T(self.lid..'Cargo_SpawnObjects') -local CargoHeading=self.CargoHeading -if offload_cargo==true or ParatrooperGroupSpawn==true then -if ParatrooperGroupSpawn==true then -self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country,10) -else -self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country) -end -else -if all_cargo_gets_destroyed==true or Cargo_over_water==true then -else -if all_cargo_survive_to_the_ground==true then -if ParatrooperGroupSpawn==true then -self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,true,Cargo_Country) -else -self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country) -end -if Container_Enclosed==true then -if ParatrooperGroupSpawn==false then -self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,"Hercules_Container_Parachute_Static",CargoHeading,false,Cargo_Country) -end -end -end -if destroy_cargo_dropped_without_parachute==true then -if Container_Enclosed==true then -if ParatrooperGroupSpawn==true then -self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country,0) -else -if self.CTLD.dropAsCargoCrate then -self:Cargo_SpawnDroppedAsCargo(Cargo_Type_name,Cargo_Content_position) -else -self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country) -self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,"Hercules_Container_Parachute_Static",CargoHeading,false,Cargo_Country) -end -end -else -self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,true,Cargo_Country) -end -end -end -end -return self -end -function CTLD_HERCULES:Calculate_Object_Height_AGL(group) -self:T(self.lid.."Calculate_Object_Height_AGL") -if group.ClassName and group.ClassName=="GROUP"then -local gcoord=group:GetCoordinate() -local height=group:GetHeight() -local lheight=gcoord:GetLandHeight() -self:T(self.lid.."Height "..height-lheight) -return height-lheight -else -if group:isExist()then -local dcsposition=group:getPosition().p -local dcsvec2={x=dcsposition.x,y=dcsposition.z} -local height=math.floor(group:getPosition().p.y-land.getHeight(dcsvec2)) -self.ObjectTracker[group.id_]=dcsposition -self:T(self.lid.."Height "..height) -return height -else -return 0 -end -end -end -function CTLD_HERCULES:Check_SurfaceType(object) -self:T(self.lid.."Check_SurfaceType") -if object:isExist()then -return land.getSurfaceType({x=object:getPosition().p.x,y=object:getPosition().p.z}) -else -return 1 -end -end -function CTLD_HERCULES:Cargo_Track(cargo,initiator) -self:T(self.lid.."Cargo_Track") -local Cargo_Drop_initiator=initiator -if cargo.Cargo_Contents~=nil then -if self:Calculate_Object_Height_AGL(cargo.Cargo_Contents)<10 then -if self:Check_SurfaceType(cargo.Cargo_Contents)==2 or self:Check_SurfaceType(cargo.Cargo_Contents)==3 then -cargo.Cargo_over_water=true -end -local dcsvec3=self.ObjectTracker[cargo.Cargo_Contents.id_]or initiator:GetVec3() -self:T("SPAWNPOSITION: ") -self:T({dcsvec3}) -local Vec2={ -x=dcsvec3.x, -y=dcsvec3.z, -} -local vec3=COORDINATE:NewFromVec2(Vec2) -self.ObjectTracker[cargo.Cargo_Contents.id_]=nil -self:Cargo_SpawnObjects(Cargo_Drop_initiator,cargo.Cargo_Drop_Direction,vec3,cargo.Cargo_Type_name,cargo.Cargo_over_water,cargo.Container_Enclosed,cargo.ParatrooperGroupSpawn,cargo.offload_cargo,cargo.all_cargo_survive_to_the_ground,cargo.all_cargo_gets_destroyed,cargo.destroy_cargo_dropped_without_parachute,cargo.Cargo_Country) -if cargo.Cargo_Contents:isExist()then -cargo.Cargo_Contents:destroy() -end -cargo.scheduleFunctionID:Stop() -cargo={} -end -end -return self -end -function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_NorthCorrection(point) -self:T(self.lid.."Calculate_Cargo_Drop_initiator_NorthCorrection") -if not point.z then -point.z=point.y -point.y=0 -end -local lat,lon=coord.LOtoLL(point) -local north_posit=coord.LLtoLO(lat+1,lon) -return math.atan2(north_posit.z-point.z,north_posit.x-point.x) -end -function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_Heading(Cargo_Drop_initiator) -self:T(self.lid.."Calculate_Cargo_Drop_initiator_Heading") -local Heading=Cargo_Drop_initiator:GetHeading() -Heading=Heading+self:Calculate_Cargo_Drop_initiator_NorthCorrection(Cargo_Drop_initiator:GetVec3()) -if Heading<0 then -Heading=Heading+(2*math.pi) -end -return Heading+0.06 -end -function CTLD_HERCULES:Cargo_Initialize(Initiator,Cargo_Contents,Cargo_Type_name,Container_Enclosed,SoldierGroup,ParatrooperGroupSpawnInit) -self:T(self.lid.."Cargo_Initialize") -local Cargo_Drop_initiator=Initiator:GetName() -if Cargo_Drop_initiator~=nil then -if ParatrooperGroupSpawnInit==true then -self:T("Paratrooper Drop") -if not self.ParatrooperCount[Cargo_Drop_initiator]then -self.ParatrooperCount[Cargo_Drop_initiator]=1 -else -self.ParatrooperCount[Cargo_Drop_initiator]=self.ParatrooperCount[Cargo_Drop_initiator]+1 -end -local Paratroopers=self.ParatrooperCount[Cargo_Drop_initiator] -self:T("Paratrooper Drop Number "..self.ParatrooperCount[Cargo_Drop_initiator]) -local SpawnParas=false -if math.fmod(Paratroopers,10)==0 then -SpawnParas=true -end -self.j=self.j+1 -self.Cargo[self.j]={} -self.Cargo[self.j].Cargo_Drop_Direction=self:Calculate_Cargo_Drop_initiator_Heading(Initiator) -self.Cargo[self.j].Cargo_Contents=Cargo_Contents -self.Cargo[self.j].Cargo_Type_name=Cargo_Type_name -self.Cargo[self.j].Container_Enclosed=Container_Enclosed -self.Cargo[self.j].ParatrooperGroupSpawn=SpawnParas -self.Cargo[self.j].Cargo_Country=Initiator:GetCountry() -if self:Calculate_Object_Height_AGL(Initiator)<5.0 then -self.Cargo[self.j].offload_cargo=true -elseif self:Calculate_Object_Height_AGL(Initiator)<10.0 then -self.Cargo[self.j].all_cargo_survive_to_the_ground=true -elseif self:Calculate_Object_Height_AGL(Initiator)<100.0 then -self.Cargo[self.j].all_cargo_gets_destroyed=true -else -self.Cargo[self.j].all_cargo_gets_destroyed=false -end -local timer=TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator) -self.Cargo[self.j].scheduleFunctionID=timer -timer:Start(1,1,600) -else -self.j=self.j+1 -self.Cargo[self.j]={} -self.Cargo[self.j].Cargo_Drop_Direction=self:Calculate_Cargo_Drop_initiator_Heading(Initiator) -self.Cargo[self.j].Cargo_Contents=Cargo_Contents -self.Cargo[self.j].Cargo_Type_name=Cargo_Type_name -self.Cargo[self.j].Container_Enclosed=Container_Enclosed -self.Cargo[self.j].ParatrooperGroupSpawn=false -self.Cargo[self.j].Cargo_Country=Initiator:GetCountry() -if self:Calculate_Object_Height_AGL(Initiator)<5.0 then -self.Cargo[self.j].offload_cargo=true -elseif self:Calculate_Object_Height_AGL(Initiator)<10.0 then -self.Cargo[self.j].all_cargo_survive_to_the_ground=true -elseif self:Calculate_Object_Height_AGL(Initiator)<100.0 then -self.Cargo[self.j].all_cargo_gets_destroyed=true -else -self.Cargo[self.j].destroy_cargo_dropped_without_parachute=true -end -local timer=TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator) -self.Cargo[self.j].scheduleFunctionID=timer -timer:Start(1,1,600) -end -end -return self -end -function CTLD_HERCULES:SetType(key,cargoType,cargoNum) -self:T(self.lid.."SetType") -self.carrierGroups[key]['cargoType']=cargoType -self.carrierGroups[key]['cargoNum']=cargoNum -return self -end -function CTLD_HERCULES:_HandleShot(Cargo_Drop_Event) -self:T(self.lid.."Shot Event ID:"..Cargo_Drop_Event.id) -if Cargo_Drop_Event.id==EVENTS.Shot then -local GT_Name="" -local SoldierGroup=false -local ParatrooperGroupSpawnInit=false -local GT_DisplayName=Weapon.getDesc(Cargo_Drop_Event.weapon).typeName:sub(15,-1) -self:T(string.format("%sCargo_Drop_Event: %s",self.lid,Weapon.getDesc(Cargo_Drop_Event.weapon).typeName)) -if(GT_DisplayName=="Squad 30 x Soldier [7950lb]")then -self:Cargo_Initialize(Cargo_Drop_Event.IniGroup,Cargo_Drop_Event.weapon,"Soldier M4 GRG",false,true,true) -end -if self.Types[GT_DisplayName]then -local GT_Name=self.Types[GT_DisplayName]['name'] -local Cargo_Container_Enclosed=self.Types[GT_DisplayName]['container'] -self:Cargo_Initialize(Cargo_Drop_Event.IniGroup,Cargo_Drop_Event.weapon,GT_Name,Cargo_Container_Enclosed) -end -end -return self -end -function CTLD_HERCULES:_HandleBirth(event) -self:T(self.lid.."Birth Event ID:"..event.id) -return self -end -end -CSAR={ -ClassName="CSAR", -verbose=0, -lid="", -coalition=1, -coalitiontxt="blue", -FreeVHFFrequencies={}, -UsedVHFFrequencies={}, -takenOff={}, -csarUnits={}, -downedPilots={}, -landedStatus={}, -addedTo={}, -woundedGroups={}, -inTransitGroups={}, -smokeMarkers={}, -heliVisibleMessage={}, -heliCloseMessage={}, -max_units=6, -hoverStatus={}, -pilotDisabled={}, -pilotLives={}, -useprefix=true, -csarPrefix={}, -template=nil, -mash={}, -smokecolor=4, -rescues=0, -rescuedpilots=0, -limitmaxdownedpilots=true, -maxdownedpilots=10, -allheligroupset=nil, -topmenuname="CSAR", -ADFRadioPwr=1000, -PilotWeight=80, -} -CSAR.AircraftType={} -CSAR.AircraftType["SA342Mistral"]=2 -CSAR.AircraftType["SA342Minigun"]=2 -CSAR.AircraftType["SA342L"]=4 -CSAR.AircraftType["SA342M"]=4 -CSAR.AircraftType["UH-1H"]=8 -CSAR.AircraftType["Mi-8MTV2"]=12 -CSAR.AircraftType["Mi-8MT"]=12 -CSAR.AircraftType["Mi-24P"]=8 -CSAR.AircraftType["Mi-24V"]=8 -CSAR.AircraftType["Bell-47"]=2 -CSAR.AircraftType["UH-60L"]=10 -CSAR.AircraftType["AH-64D_BLK_II"]=2 -CSAR.AircraftType["Bronco-OV-10A"]=2 -CSAR.version="1.0.19" -function CSAR:New(Coalition,Template,Alias) -local self=BASE:Inherit(self,FSM:New()) -BASE:T({Coalition,Template,Alias}) -if Coalition and type(Coalition)=="string"then -if Coalition=="blue"then -self.coalition=coalition.side.BLUE -self.coalitiontxt=Coalition -elseif Coalition=="red"then -self.coalition=coalition.side.RED -self.coalitiontxt=Coalition -elseif Coalition=="neutral"then -self.coalition=coalition.side.NEUTRAL -self.coalitiontxt=Coalition -else -self:E("ERROR: Unknown coalition in CSAR!") -end -else -self.coalition=Coalition -self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition)) -end -if Alias then -self.alias=tostring(Alias) -else -self.alias="Red Cross" -if self.coalition then -if self.coalition==coalition.side.RED then -self.alias="IFRC" -elseif self.coalition==coalition.side.BLUE then -self.alias="CSAR" -end -end -end -self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","Status","*") -self:AddTransition("*","PilotDown","*") -self:AddTransition("*","Approach","*") -self:AddTransition("*","Landed","*") -self:AddTransition("*","Boarded","*") -self:AddTransition("*","Returning","*") -self:AddTransition("*","Rescued","*") -self:AddTransition("*","KIA","*") -self:AddTransition("*","Load","*") -self:AddTransition("*","Save","*") -self:AddTransition("*","Stop","Stopped") -self.addedTo={} -self.allheligroupset={} -self.csarUnits={} -self.FreeVHFFrequencies={} -self.heliVisibleMessage={} -self.heliCloseMessage={} -self.hoverStatus={} -self.inTransitGroups={} -self.landedStatus={} -self.lastCrash={} -self.takenOff={} -self.smokeMarkers={} -self.UsedVHFFrequencies={} -self.woundedGroups={} -self.downedPilots={} -self.downedpilotcounter=1 -self.rescues=0 -self.rescuedpilots=0 -self.csarOncrash=false -self.allowDownedPilotCAcontrol=false -self.enableForAI=false -self.smokecolor=4 -self.coordtype=2 -self.immortalcrew=true -self.invisiblecrew=false -self.messageTime=15 -self.pilotRuntoExtractPoint=true -self.loadDistance=75 -self.extractDistance=500 -self.loadtimemax=135 -self.radioSound="beacon.ogg" -self.beaconRefresher=29 -self.allowFARPRescue=true -self.FARPRescueDistance=1000 -self.max_units=6 -self.useprefix=true -self.csarPrefix={"helicargo","MEDEVAC"} -self.template=Template or"generic" -self.mashprefix={"MASH"} -self.autosmoke=false -self.autosmokedistance=2000 -self.limitmaxdownedpilots=true -self.maxdownedpilots=25 -self:_GenerateVHFrequencies() -self.approachdist_far=5000 -self.approachdist_near=3000 -self.pilotmustopendoors=false -self.suppressmessages=false -self.rescuehoverheight=20 -self.rescuehoverdistance=10 -self.countryblue=country.id.USA -self.countryred=country.id.RUSSIA -self.countryneutral=country.id.UN_PEACEKEEPERS -self.csarUsePara=false -self.wetfeettemplate=nil -self.usewetfeet=false -self.allowbronco=false -self.ADFRadioPwr=1000 -self.PilotWeight=80 -self.useSRS=false -self.SRSPath="E:\\Program Files\\DCS-SimpleRadio-Standalone" -self.SRSchannel=300 -self.SRSModulation=radio.modulation.AM -self.SRSport=5002 -self.SRSCulture="en-GB" -self.SRSVoice=nil -self.SRSGPathToCredentials=nil -self.SRSVolume=1.0 -self.SRSGender="male" -self.CSARVoice=MSRS.Voices.Google.Standard.en_US_Standard_A -self.CSARVoiceMS=MSRS.Voices.Microsoft.Hedda -self.coordinate=nil -local AliaS=string.gsub(self.alias," ","_") -self.filename=string.format("CSAR_%s_Persist.csv",AliaS) -self.enableLoadSave=false -self.filepath=nil -self.saveinterval=600 -return self -end -function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet) -self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername}) -local DownedPilot={} -DownedPilot.desc=Description or"" -DownedPilot.frequency=Frequency or 0 -DownedPilot.index=self.downedpilotcounter -DownedPilot.name=Groupname or"" -DownedPilot.originalUnit=OriginalUnit or"" -DownedPilot.player=Playername or"" -DownedPilot.side=Side or 0 -DownedPilot.typename=Typename or"" -DownedPilot.group=Group -DownedPilot.timestamp=0 -DownedPilot.alive=true -DownedPilot.wetfeet=Wetfeet or false -local PilotTable=self.downedPilots -local counter=self.downedpilotcounter -PilotTable[counter]={} -PilotTable[counter]=DownedPilot -self:T({Table=PilotTable}) -self.downedPilots=PilotTable -self.downedpilotcounter=self.downedpilotcounter+1 -return self -end -function CSAR:_PilotsOnboard(_heliName) -self:T(self.lid.." _PilotsOnboard") -local count=0 -if self.inTransitGroups[_heliName]then -for _,_group in pairs(self.inTransitGroups[_heliName])do -count=count+1 -end -end -return count -end -function CSAR:_DoubleEjection(_unitname) -if self.lastCrash[_unitname]then -local _time=self.lastCrash[_unitname] -if timer.getTime()-_time<10 then -self:E(self.lid.."Caught double ejection!") -return true -end -end -self.lastCrash[_unitname]=timer.getTime() -return false -end -function CSAR:AddPlayerTask(PlayerTask) -self:T(self.lid.." AddPlayerTask") -if not self.PlayerTaskQueue then -self.PlayerTaskQueue=FIFO:New() -end -self.PlayerTaskQueue:Push(PlayerTask,PlayerTask.PlayerTaskNr) -return self -end -function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet) -self:T({country,point,frequency,tostring(wetfeet)}) -local freq=frequency or 1000 -local freq=freq/1000 -for i=1,10 do -math.random(i,10000) -end -if point:IsSurfaceTypeWater()or wetfeet then -point.y=0 -end -local template=self.template -if self.usewetfeet and wetfeet then -template=self.wetfeettemplate -end -local alias=string.format("Pilot %.2fkHz-%d",freq,math.random(1,99)) -local coalition=self.coalition -local pilotcacontrol=self.allowDownedPilotCAcontrol -local _spawnedGroup=SPAWN -:NewWithAlias(template,alias) -:InitCoalition(coalition) -:InitCountry(country) -:InitAIOnOff(pilotcacontrol) -:InitDelayOff() -:SpawnFromCoordinate(point) -return _spawnedGroup,alias -end -function CSAR:_AddSpecialOptions(group) -self:T(self.lid.." _AddSpecialOptions") -self:T({group}) -local immortalcrew=self.immortalcrew -local invisiblecrew=self.invisiblecrew -if immortalcrew then -local _setImmortal={ -id='SetImmortal', -params={ -value=true -} -} -group:SetCommand(_setImmortal) -end -if invisiblecrew then -local _setInvisible={ -id='SetInvisible', -params={ -value=true -} -} -group:SetCommand(_setInvisible) -end -group:OptionAlarmStateGreen() -group:OptionROEHoldFire() -return self -end -function CSAR:_AddCsar(_coalition,_country,_point,_typeName,_unitName,_playerName,_freq,noMessage,_description,forcedesc) -self:T(self.lid.." _AddCsar") -self:T({_coalition,_country,_point,_typeName,_unitName,_playerName,_freq,noMessage,_description}) -local template=self.template -local wetfeet=false -local surface=_point:GetSurfaceType() -if surface==land.SurfaceType.WATER then -wetfeet=true -end -if not _freq then -_freq=self:_GenerateADFFrequency() -if not _freq then _freq=333000 end -end -local _spawnedGroup,_alias=self:_SpawnPilotInField(_country,_point,_freq,wetfeet) -local _typeName=_typeName or"Pilot" -if not noMessage then -if _freq~=0 then -self:_DisplayToAllSAR("MAYDAY MAYDAY! ".._typeName.." is down. ",self.coalition,self.messageTime) -else -self:_DisplayToAllSAR("Troops In Contact. ".._typeName.." requests CASEVAC. ",self.coalition,self.messageTime) -end -end -if(_freq and _freq~=0)then -self:_AddBeaconToGroup(_spawnedGroup,_freq) -end -self:_AddSpecialOptions(_spawnedGroup) -local _text=_description -if not forcedesc then -if _playerName~=nil then -if _freq~=0 then -_text="Pilot ".._playerName -else -_text="TIC - ".._playerName -end -elseif _unitName~=nil then -if _freq~=0 then -_text="AI Pilot of ".._unitName -else -_text="TIC - ".._unitName -end -end -end -self:T({_spawnedGroup,_alias}) -local _GroupName=_spawnedGroup:GetName()or _alias -self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet) -self:_InitSARForPilot(_spawnedGroup,_unitName,_freq,noMessage,_playerName) -return self -end -function CSAR:_SpawnCsarAtZone(_zone,_coalition,_description,_randomPoint,_nomessage,unitname,typename,forcedesc) -self:T(self.lid.." _SpawnCsarAtZone") -local freq=self:_GenerateADFFrequency() -local _triggerZone=nil -if type(_zone)=="string"then -_triggerZone=ZONE:New(_zone) -elseif type(_zone)=="table"and _zone.ClassName then -if string.find(_zone.ClassName,"ZONE",1)then -_triggerZone=_zone -end -end -if _triggerZone==nil then -self:E(self.lid.."ERROR: Can\'t find zone called ".._zone,10) -return -end -local _description=_description or"PoW" -local unitname=unitname or"Old Rusty" -local typename=typename or"Phantom II" -local pos={} -if _randomPoint then -local _pos=_triggerZone:GetRandomPointVec3() -pos=COORDINATE:NewFromVec3(_pos) -else -pos=_triggerZone:GetCoordinate() -end -local _country=0 -if _coalition==coalition.side.BLUE then -_country=self.countryblue -elseif _coalition==coalition.side.RED then -_country=self.countryred -else -_country=self.countryneutral -end -self:_AddCsar(_coalition,_country,pos,typename,unitname,_description,freq,_nomessage,_description,forcedesc) -return self -end -function CSAR:SpawnCSARAtZone(Zone,Coalition,Description,RandomPoint,Nomessage,Unitname,Typename,Forcedesc) -self:_SpawnCsarAtZone(Zone,Coalition,Description,RandomPoint,Nomessage,Unitname,Typename,Forcedesc) -return self -end -function CSAR:_SpawnCASEVAC(_Point,_coalition,_description,_nomessage,unitname,typename,forcedesc) -self:T(self.lid.." _SpawnCASEVAC") -local _description=_description or"CASEVAC" -local unitname=unitname or"CASEVAC" -local typename=typename or"Ground Commander" -local pos={} -pos=_Point -local _country=0 -if _coalition==coalition.side.BLUE then -_country=self.countryblue -elseif _coalition==coalition.side.RED then -_country=self.countryred -else -_country=self.countryneutral -end -self:_AddCsar(_coalition,_country,pos,typename,unitname,_description,0,_nomessage,_description,forcedesc) -return self -end -function CSAR:SpawnCASEVAC(Point,Coalition,Description,Nomessage,Unitname,Typename,Forcedesc) -self:_SpawnCASEVAC(Point,Coalition,Description,Nomessage,Unitname,Typename,Forcedesc) -return self -end -function CSAR:_EventHandler(EventData) -self:T(self.lid.." _EventHandler") -self:T({Event=EventData.id}) -local _event=EventData -if self.enableForAI==false and _event.IniPlayerName==nil then -return self -end -if _event==nil or _event.initiator==nil then -return self -elseif _event.id==EVENTS.Takeoff then -self:T(self.lid.." Event unit - Takeoff") -local _coalition=_event.IniCoalition -if _coalition~=self.coalition then -return self -end -if _event.IniGroupName then -self.takenOff[_event.IniUnitName]=true -end -return self -elseif _event.id==EVENTS.PlayerEnterAircraft or _event.id==EVENTS.PlayerEnterUnit then -self:T(self.lid.." Event unit - Player Enter") -local _coalition=_event.IniCoalition -self:T("Coalition = "..UTILS.GetCoalitionName(_coalition)) -if _coalition~=self.coalition then -return self -end -if _event.IniPlayerName then -self.takenOff[_event.IniPlayerName]=nil -end -self:T("Taken Off: "..tostring(_event.IniUnit:InAir(true))) -if _event.IniUnit:InAir(true)then -self.takenOff[_event.IniPlayerName]=true -end -local _unit=_event.IniUnit -local _group=_event.IniGroup -local function IsBronco(Group) -local grp=Group -local typename=grp:GetTypeName() -self:T(typename) -if typename=="Bronco-OV-10A"then return true end -return false -end -if _unit:IsHelicopter()or _group:IsHelicopter()or IsBronco(_group)then -self:_AddMedevacMenuItem() -end -return self -elseif(_event.id==EVENTS.PilotDead and self.csarOncrash==false)then -self:T(self.lid.." Event unit - Pilot Dead") -local _unit=_event.IniUnit -local _unitname=_event.IniUnitName -local _group=_event.IniGroup -if _unit==nil then -return self -end -local _coalition=_event.IniCoalition -if _coalition~=self.coalition then -return self -end -if self.takenOff[_event.IniUnitName]==true or _group:IsAirborne()then -if self:_DoubleEjection(_unitname)then -return self -end -else -self:T(self.lid.." Pilot has not taken off, ignore") -end -return self -elseif _event.id==EVENTS.PilotDead or _event.id==EVENTS.Ejection then -if _event.id==EVENTS.PilotDead and self.csarOncrash==false then -return self -end -self:T(self.lid.." Event unit - Pilot Ejected") -local _unit=_event.IniUnit -local _unitname=_event.IniUnitName -local _group=_event.IniGroup -self:T({_unit.UnitName,_unitname,_group.GroupName}) -if _unit==nil then -self:T("Unit NIL!") -return self -end -local _coalition=_group:GetCoalition() -if _coalition~=self.coalition then -self:T("Wrong coalition! Coalition = "..UTILS.GetCoalitionName(_coalition)) -return self -end -self:T("Airborne: "..tostring(_group:IsAirborne())) -self:T("Taken Off: "..tostring(self.takenOff[_event.IniUnitName])) -if not self.takenOff[_event.IniUnitName]and not _group:IsAirborne()then -self:T(self.lid.." Pilot has not taken off, ignore") -end -if self:_DoubleEjection(_unitname)then -self:T("Double Ejection!") -return self -end -if self.limitmaxdownedpilots and self:_ReachedPilotLimit()then -self:T("Maxed Downed Pilot!") -return self -end -local wetfeet=false -local initdcscoord=nil -local initcoord=nil -if _event.id==EVENTS.Ejection then -initdcscoord=_event.TgtDCSUnit:getPoint() -initcoord=COORDINATE:NewFromVec3(initdcscoord) -self:T({initdcscoord}) -else -initdcscoord=_event.IniDCSUnit:getPoint() -initcoord=COORDINATE:NewFromVec3(initdcscoord) -self:T({initdcscoord}) -end -local surface=initcoord:GetSurfaceType() -if surface==land.SurfaceType.WATER then -self:T("Wet feet!") -wetfeet=true -end -if self.csarUsePara==false or(self.csarUsePara and wetfeet)then -local _freq=self:_GenerateADFFrequency() -self:_AddCsar(_coalition,_unit:GetCountry(),initcoord,_unit:GetTypeName(),_unit:GetName(),_event.IniPlayerName,_freq,false,"none") -return self -end -elseif _event.id==EVENTS.Land then -self:T(self.lid.." Landing") -if _event.IniUnitName then -self.takenOff[_event.IniUnitName]=nil -end -if self.allowFARPRescue then -local _unit=_event.IniUnit -if _unit==nil then -self:T(self.lid.." Unit nil on landing") -return self -end -local _coalition=_event.IniGroup:GetCoalition() -if _coalition~=self.coalition then -self:T(self.lid.." Wrong coalition") -return self -end -self.takenOff[_event.IniUnitName]=nil -local _place=_event.Place -if _place==nil then -self:T(self.lid.." Landing Place Nil") -return self -end -if self.inTransitGroups[_event.IniUnitName]==nil then -return self -end -if _place:GetCoalition()==self.coalition or _place:GetCoalition()==coalition.side.NEUTRAL then -self:__Landed(2,_event.IniUnitName,_place) -self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true) -else -self:T(string.format("Airfield %d, Unit %d",_place:GetCoalition(),_unit:GetCoalition())) -end -end -return self -end -if(_event.id==EVENTS.LandingAfterEjection and self.csarUsePara==true)then -self:T("LANDING_AFTER_EJECTION") -local _LandingPos=COORDINATE:NewFromVec3(_event.initiator:getPosition().p) -local _unitname="Aircraft" -local _typename="Ejected Pilot" -local _country=_event.initiator:getCountry() -local _coalition=coalition.getCountryCoalition(_country) -self:T("Country = ".._country.." Coalition = ".._coalition) -if _coalition==self.coalition then -local _freq=self:_GenerateADFFrequency() -self:I({coalition=_coalition,country=_country,coord=_LandingPos,name=_unitname,player=_event.IniPlayerName,freq=_freq}) -self:_AddCsar(_coalition,_country,_LandingPos,nil,_unitname,_event.IniPlayerName,_freq,false,"none") -Unit.destroy(_event.initiator) -end -end -return self -end -function CSAR:_InitSARForPilot(_downedGroup,_GroupName,_freq,_nomessage,_playername) -self:T(self.lid.." _InitSARForPilot") -local _leader=_downedGroup:GetUnit(1) -local _groupName=_GroupName -local _freqk=_freq/1000 -local _coordinatesText=self:_GetPositionOfWounded(_downedGroup) -local _leadername=_leader:GetName() -if not _nomessage then -if _freq~=0 then -local _text=string.format("%s requests SAR at %s, beacon at %.2f KHz",_groupName,_coordinatesText,_freqk) -self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) -else -local _text=string.format("Pickup Zone at %s.",_coordinatesText) -self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) -end -end -for _,_heliName in pairs(self.csarUnits)do -self:_CheckWoundedGroupStatus(_heliName,_groupName) -end -self:__PilotDown(2,_downedGroup,_freqk,_groupName,_coordinatesText,_playername) -return self -end -function CSAR:_CheckNameInDownedPilots(name) -local PilotTable=self.downedPilots -local found=false -local table=nil -for _,_pilot in pairs(PilotTable)do -if _pilot.name==name and _pilot.alive==true then -found=true -table=_pilot -break -end -end -return found,table -end -function CSAR:_RemoveNameFromDownedPilots(name,force) -local PilotTable=self.downedPilots -local found=false -for _index,_pilot in pairs(PilotTable)do -if _pilot.name==name then -self.downedPilots[_index].alive=false -end -end -return found -end -function CSAR:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) -if not ShortCallsign or ShortCallsign==false then -self.ShortCallsign=false -else -self.ShortCallsign=true -end -self.Keepnumber=Keepnumber or false -self.CallsignTranslations=CallsignTranslations -return self -end -function CSAR:_GetCustomCallSign(UnitName) -local callsign=UnitName -local unit=UNIT:FindByName(UnitName) -if unit and unit:IsAlive()then -local group=unit:GetGroup() -callsign=group:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) -end -return callsign -end -function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) -self:T(self.lid.." _CheckWoundedGroupStatus") -local _heliName=heliname -local _woundedGroupName=woundedgroupname -self:T({Heli=_heliName,Downed=_woundedGroupName}) -local _found,_downedpilot=self:_CheckNameInDownedPilots(_woundedGroupName) -if not _found then -self:T("...not found in list!") -return -end -local _woundedGroup=_downedpilot.group -if _woundedGroup~=nil and _woundedGroup:IsAlive()then -local _heliUnit=self:_GetSARHeli(_heliName) -local _lookupKeyHeli=_heliName.."_".._woundedGroupName -if _heliUnit==nil then -self.heliVisibleMessage[_lookupKeyHeli]=nil -self.heliCloseMessage[_lookupKeyHeli]=nil -self.landedStatus[_lookupKeyHeli]=nil -self:T("...heliunit nil!") -return -end -local _heliCoord=_heliUnit:GetCoordinate() -local _leaderCoord=_woundedGroup:GetCoordinate() -local _distance=self:_GetDistance(_heliCoord,_leaderCoord) -if(self.autosmoke==true)and(_distance0 then -if self:_CheckCloseWoundedGroup(_distance,_heliUnit,_heliName,_woundedGroup,_woundedGroupName)==true then -_downedpilot.timestamp=timer.getAbsTime() -self:__Approach(-5,heliname,woundedgroupname) -end -elseif _distance>=self.approachdist_near and _distance_lastSmoke then -local _smokecolor=self.smokecolor -local _smokecoord=_woundedLeader:GetCoordinate():Translate(6,math.random(1,360)) -_smokecoord:Smoke(_smokecolor) -self.smokeMarkers[_woundedGroupName]=timer.getTime()+300 -end -return self -end -function CSAR:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName) -self:T(self.lid.." _PickupUnit") -local _heliName=_heliUnit:GetName() -local _groups=self.inTransitGroups[_heliName] -local _unitsInHelicopter=self:_PilotsOnboard(_heliName) -if not _groups then -self.inTransitGroups[_heliName]={} -_groups=self.inTransitGroups[_heliName] -end -local _maxUnits=self.AircraftType[_heliUnit:GetTypeName()] -if _maxUnits==nil then -_maxUnits=self.max_units -end -if _unitsInHelicopter+1>_maxUnits then -self:_DisplayMessageToSAR(_heliUnit,string.format("%s, %s. We\'re already crammed with %d guys! Sorry!",_pilotName,self:_GetCustomCallSign(_heliName),_unitsInHelicopter,_unitsInHelicopter),self.messageTime,false,false,true) -return self -end -local found,downedgrouptable=self:_CheckNameInDownedPilots(_woundedGroupName) -local grouptable=downedgrouptable -self.inTransitGroups[_heliName][_woundedGroupName]= -{ -originalUnit=grouptable.originalUnit, -woundedGroup=_woundedGroupName, -side=self.coalition, -desc=grouptable.desc, -player=grouptable.player, -} -_woundedGroup:Destroy(false) -self:_RemoveNameFromDownedPilots(_woundedGroupName,true) -self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s I\'m in! Get to the MASH ASAP! ",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,true,true) -self:_UpdateUnitCargoMass(_heliName) -self:__Boarded(5,_heliName,_woundedGroupName,grouptable.desc) -return self -end -function CSAR:_UpdateUnitCargoMass(_heliName) -self:T(self.lid.." _UpdateUnitCargoMass") -local calculatedMass=self:_PilotsOnboard(_heliName)*(self.PilotWeight or 80) -local Unit=UNIT:FindByName(_heliName) -if Unit then -Unit:SetUnitInternalCargo(calculatedMass) -end -return self -end -function CSAR:_OrderGroupToMoveToPoint(_leader,_destination) -self:T(self.lid.." _OrderGroupToMoveToPoint") -local group=_leader -local coordinate=_destination:GetVec2() -group:SetAIOn() -group:RouteToVec2(coordinate,5) -return self -end -function CSAR:_IsLoadingDoorOpen(unit_name) -self:T(self.lid.." _IsLoadingDoorOpen") -return UTILS.IsLoadingDoorOpen(unit_name) -end -function CSAR:_CheckCloseWoundedGroup(_distance,_heliUnit,_heliName,_woundedGroup,_woundedGroupName) -self:T(self.lid.." _CheckCloseWoundedGroup") -local _woundedLeader=_woundedGroup -local _lookupKeyHeli=_heliUnit:GetName().."_".._woundedGroupName -local _found,_pilotable=self:_CheckNameInDownedPilots(_woundedGroupName) -local _pilotName=_pilotable.desc -local _reset=true -if(_distance<500)then -if self.heliCloseMessage[_lookupKeyHeli]==nil then -if self.autosmoke==true then -self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s. You\'re close now! Land or hover at the smoke.",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,false,true) -else -self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,false,true) -end -self.heliCloseMessage[_lookupKeyHeli]=true -end -if not _heliUnit:InAir()then -if self.pilotRuntoExtractPoint==true then -if(_distance0 then -self:_DisplayMessageToSAR(_heliUnit,"Hovering above ".._pilotName..". \n\nHold hover for ".._time.." seconds to winch them up. \n\nIf the countdown stops you\'re too far away!",self.messageTime,true) -else -if self.pilotmustopendoors and(self:_IsLoadingDoorOpen(_heliName)==false)then -self:_DisplayMessageToSAR(_heliUnit,"Open the door to let me in!",self.messageTime,true,true) -return false -else -self.hoverStatus[_lookupKeyHeli]=nil -self:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName) -return true -end -end -_reset=false -else -self:_DisplayMessageToSAR(_heliUnit,"Too high to winch ".._pilotName.." \nReduce height and hover for 10 seconds!",self.messageTime,true,true) -return false -end -end -end -end -end -if _reset then -self.hoverStatus[_lookupKeyHeli]=nil -end -if _distance<500 then -return true -else -return false -end -end -function CSAR:_ScheduledSARFlight(heliname,groupname,isairport) -self:T(self.lid.." _ScheduledSARFlight") -self:T({heliname,groupname}) -local _heliUnit=self:_GetSARHeli(heliname) -local _woundedGroupName=groupname -if(_heliUnit==nil)then -self.inTransitGroups[heliname]=nil -return -end -if self.inTransitGroups[heliname]==nil or self.inTransitGroups[heliname][_woundedGroupName]==nil then -return -end -local _dist=self:_GetClosestMASH(_heliUnit) -if _dist==-1 then -return -end -if(_distsmokedist then smokedist=self.approachdist_far end -if _closest~=nil and _closest.pilot~=nil and _closest.distance>0 and _closest.distance0 and _closest.distance12 then clock=clock-12 end -end -return clock -end -function CSAR:_AddBeaconToGroup(_group,_freq) -self:T(self.lid.." _AddBeaconToGroup") -local _group=_group -if _group==nil then -for _i,_current in ipairs(self.UsedVHFFrequencies)do -if _current==_freq then -table.insert(self.FreeVHFFrequencies,_freq) -table.remove(self.UsedVHFFrequencies,_i) -end -end -return -end -if _group:IsAlive()then -local _radioUnit=_group:GetUnit(1) -if _radioUnit then -local name=_radioUnit:GetName() -local Frequency=_freq -local name=_radioUnit:GetName() -local Sound="l10n/DEFAULT/"..self.radioSound -local vec3=_radioUnit:GetVec3()or _radioUnit:GetPositionVec3()or{x=0,y=0,z=0} -trigger.action.radioTransmission(Sound,vec3,0,false,Frequency,self.ADFRadioPwr or 1000,name..math.random(1,10000)) -end -end -return self -end -function CSAR:_RefreshRadioBeacons() -self:T(self.lid.." _RefreshRadioBeacons") -if self:_CountActiveDownedPilots()>0 then -local PilotTable=self.downedPilots -for _,_pilot in pairs(PilotTable)do -self:T({_pilot}) -local pilot=_pilot -local group=pilot.group -local frequency=pilot.frequency or 0 -if group and group:IsAlive()and frequency>0 then -self:_AddBeaconToGroup(group,frequency) -end -end -end -return self -end -function CSAR:_CountActiveDownedPilots() -self:T(self.lid.." _CountActiveDownedPilots") -local PilotsInFieldN=0 -for _,_unitName in pairs(self.downedPilots)do -self:T({_unitName.desc}) -if _unitName.alive==true then -PilotsInFieldN=PilotsInFieldN+1 -end -end -return PilotsInFieldN -end -function CSAR:_ReachedPilotLimit() -self:T(self.lid.." _ReachedPilotLimit") -local limit=self.maxdownedpilots -local islimited=self.limitmaxdownedpilots -local count=self:_CountActiveDownedPilots() -if islimited and(count>=limit)then -return true -else -return false -end -end -function CSAR:onafterStart(From,Event,To) -self:T({From,Event,To}) -self:I(self.lid.."Started ("..self.version..")") -self:HandleEvent(EVENTS.Takeoff,self._EventHandler) -self:HandleEvent(EVENTS.Land,self._EventHandler) -self:HandleEvent(EVENTS.Ejection,self._EventHandler) -self:HandleEvent(EVENTS.LandingAfterEjection,self._EventHandler) -self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) -self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler) -self:HandleEvent(EVENTS.PilotDead,self._EventHandler) -if self.allowbronco then -local prefixes=self.csarPrefix or{} -self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterStart() -elseif self.useprefix then -local prefixes=self.csarPrefix or{} -self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart() -else -self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() -end -self.mash=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -if not self.coordinate then -local csarhq=self.mash:GetRandom() -if csarhq then -self.coordinate=csarhq:GetCoordinate() -end -end -if self.wetfeettemplate then -self.usewetfeet=true -end -if self.useSRS then -local path=self.SRSPath -local modulation=self.SRSModulation -local channel=self.SRSchannel -self.msrs=MSRS:New(path,channel,modulation) -self.msrs:SetPort(self.SRSport) -self.msrs:SetLabel("CSAR") -self.msrs:SetCulture(self.SRSCulture) -self.msrs:SetCoalition(self.coalition) -self.msrs:SetVoice(self.SRSVoice) -self.msrs:SetGender(self.SRSGender) -if self.SRSGPathToCredentials then -self.msrs:SetProviderOptionsGoogle(self.SRSGPathToCredentials,self.SRSGPathToCredentials) -self.msrs:SetProvider(MSRS.Provider.GOOGLE) -end -self.msrs:SetVolume(self.SRSVolume) -self.msrs:SetLabel("CSAR") -self.SRSQueue=MSRSQUEUE:New("CSAR") -end -self:__Status(-10) -if self.enableLoadSave then -local interval=self.saveinterval -local filename=self.filename -local filepath=self.filepath -self:__Save(interval,filepath,filename) -end -return self -end -function CSAR:_CheckDownedPilotTable() -local pilots=self.downedPilots -local npilots={} -for _ind,_entry in pairs(pilots)do -local _group=_entry.group -if _group:IsAlive()then -npilots[_ind]=_entry -else -if _entry.alive then -self:__KIA(1,_entry.desc) -end -end -end -self.downedPilots=npilots -return self -end -function CSAR:onbeforeStatus(From,Event,To) -self:T({From,Event,To}) -self:_AddMedevacMenuItem() -if not self.BeaconTimer or(self.BeaconTimer and not self.BeaconTimer:IsRunning())then -self.BeaconTimer=TIMER:New(self._RefreshRadioBeacons,self) -self.BeaconTimer:Start(2,self.beaconRefresher) -end -self:_CheckDownedPilotTable() -for _,_sar in pairs(self.csarUnits)do -local PilotTable=self.downedPilots -for _,_entry in pairs(PilotTable)do -if _entry.alive then -local entry=_entry -local name=entry.name -local timestamp=entry.timestamp or 0 -local now=timer.getAbsTime() -if now-timestamp>17 then -self:_CheckWoundedGroupStatus(_sar,name) -end -end -end -end -return self -end -function CSAR:onafterStatus(From,Event,To) -self:T({From,Event,To}) -local NumberOfSARPilots=0 -for _,_unitName in pairs(self.csarUnits)do -NumberOfSARPilots=NumberOfSARPilots+1 -end -local PilotsInFieldN=self:_CountActiveDownedPilots() -local PilotsBoarded=0 -for _,_unitName in pairs(self.inTransitGroups)do -for _,_units in pairs(_unitName)do -PilotsBoarded=PilotsBoarded+1 -end -end -if self.verbose>0 then -local text=string.format("%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", -self.lid,NumberOfSARPilots,PilotsInFieldN,self.maxdownedpilots,PilotsBoarded,self.rescues,self.rescuedpilots) -self:T(text) -if self.verbose<2 then -self:I(text) -elseif self.verbose>1 then -self:I(text) -local m=MESSAGE:New(text,"10","Status",true):ToCoalition(self.coalition) -end -end -self:__Status(-20) -return self -end -function CSAR:onafterStop(From,Event,To) -self:T({From,Event,To}) -self:UnHandleEvent(EVENTS.Takeoff) -self:UnHandleEvent(EVENTS.Land) -self:UnHandleEvent(EVENTS.Ejection) -self:UnHandleEvent(EVENTS.LandingAfterEjection) -self:UnHandleEvent(EVENTS.PlayerEnterUnit) -self:UnHandleEvent(EVENTS.PlayerEnterAircraft) -self:UnHandleEvent(EVENTS.PilotDead) -self:T(self.lid.."Stopped.") -return self -end -function CSAR:onbeforeApproach(From,Event,To,Heliname,Woundedgroupname) -self:T({From,Event,To,Heliname,Woundedgroupname}) -self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname) -return self -end -function CSAR:onbeforeBoarded(From,Event,To,Heliname,Woundedgroupname) -self:T({From,Event,To,Heliname,Woundedgroupname}) -self:_ScheduledSARFlight(Heliname,Woundedgroupname) -local Unit=UNIT:FindByName(Heliname) -if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then -local playername=Unit:GetPlayerName() -local dropcoord=Unit:GetCoordinate()or COORDINATE:New(0,0,0) -local dropvec2=dropcoord:GetVec2() -self.PlayerTaskQueue:ForEach( -function(Task) -local task=Task -local subtype=task:GetSubType() -if Event==subtype and not task:IsDone()then -local targetzone=task.Target:GetObject() -if(targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and targetzone:IsVec2InZone(dropvec2)) -or(string.find(task.CSARPilotName,Woundedgroupname))then -if task.Clients:HasUniqueID(playername)then -task:__Success(-1) -end -end -end -end -) -end -return self -end -function CSAR:onbeforeReturning(From,Event,To,Heliname,Woundedgroupname,IsAirPort) -self:T({From,Event,To,Heliname,Woundedgroupname}) -self:_ScheduledSARFlight(Heliname,Woundedgroupname,IsAirPort) -return self -end -function CSAR:onbeforeRescued(From,Event,To,HeliUnit,HeliName,PilotsSaved) -self:T({From,Event,To,HeliName,HeliUnit}) -self.rescues=self.rescues+1 -self.rescuedpilots=self.rescuedpilots+PilotsSaved -local Unit=HeliUnit or UNIT:FindByName(HeliName) -if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then -local playername=Unit:GetPlayerName() -self.PlayerTaskQueue:ForEach( -function(Task) -local task=Task -local subtype=task:GetSubType() -if Event==subtype and not task:IsDone()then -if task.Clients:HasUniqueID(playername)then -task:__Success(-1) -end -end -end -) -end -return self -end -function CSAR:onbeforePilotDown(From,Event,To,Group,Frequency,Leadername,CoordinatesText,Playername) -self:T({From,Event,To,Group,Frequency,Leadername,CoordinatesText,tostring(Playername)}) -return self -end -function CSAR:onbeforeLanded(From,Event,To,HeliName,Airbase) -self:T({From,Event,To,HeliName,Airbase}) -return self -end -function CSAR:onbeforeSave(From,Event,To,path,filename) -self:T({From,Event,To,path,filename}) -if not self.enableLoadSave then -return self -end -if not io then -self:E(self.lid.."ERROR: io not desanitized. Can't save current state.") -return false -end -if path==nil and not lfs then -self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") -end -return true -end -function CSAR:onafterSave(From,Event,To,path,filename) -self:T({From,Event,To,path,filename}) -if not self.enableLoadSave then -return self -end -local function _savefile(filename,data) -local f=assert(io.open(filename,"wb")) -f:write(data) -f:close() -end -if lfs then -path=self.filepath or lfs.writedir() -end -filename=filename or self.filename -if path~=nil then -filename=path.."\\"..filename -end -local pilots=self.downedPilots -local data="playerName,x,y,z,coalition,country,description,typeName,unitName,freq\n" -local n=0 -for _,_grp in pairs(pilots)do -local DownedPilot=_grp -if DownedPilot and DownedPilot.alive then -local playerName=DownedPilot.player -local group=DownedPilot.group -local coalition=group:GetCoalition() -local country=group:GetCountry() -local description=DownedPilot.desc -local typeName=DownedPilot.typename -local freq=DownedPilot.frequency -local location=group:GetVec3() -local unitName=DownedPilot.originalUnit -local txt=string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq) -self:I(self.lid.."Saving to CSAR File: "..txt) -data=data..txt -end -end -_savefile(filename,data) -if self.enableLoadSave then -local interval=self.saveinterval -local filename=self.filename -local filepath=self.filepath -self:__Save(interval,filepath,filename) -end -return self -end -function CSAR:onbeforeLoad(From,Event,To,path,filename) -self:T({From,Event,To,path,filename}) -if not self.enableLoadSave then -return self -end -local function _fileexists(name) -local f=io.open(name,"r") -if f~=nil then -io.close(f) -return true -else -return false -end -end -filename=filename or self.filename -path=path or self.filepath -if not io then -self:E(self.lid.."WARNING: io not desanitized. Cannot load file.") -return false -end -if path==nil and not lfs then -self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") -end -if lfs then -path=path or lfs.writedir() -end -if path~=nil then -filename=path.."\\"..filename -end -local exists=_fileexists(filename) -if exists then -return true -else -self:E(self.lid..string.format("WARNING: State file %s might not exist.",filename)) -return false -end -end -function CSAR:onafterLoad(From,Event,To,path,filename) -self:T({From,Event,To,path,filename}) -if not self.enableLoadSave then -return self -end -local function _loadfile(filename) -local f=assert(io.open(filename,"rb")) -local data=f:read("*all") -f:close() -return data -end -filename=filename or self.filename -path=path or self.filepath -if lfs then -path=path or lfs.writedir() -end -if path~=nil then -filename=path.."\\"..filename -end -local text=string.format("Loading CSAR state from file %s",filename) -MESSAGE:New(text,10):ToAllIf(self.Debug) -self:I(self.lid..text) -local file=assert(io.open(filename,"rb")) -local loadeddata={} -for line in file:lines()do -loadeddata[#loadeddata+1]=line -end -file:close() -table.remove(loadeddata,1) -for _id,_entry in pairs(loadeddata)do -local dataset=UTILS.Split(_entry,",") -local playerName=dataset[1] -local vec3={} -vec3.x=tonumber(dataset[2]) -vec3.y=tonumber(dataset[3]) -vec3.z=tonumber(dataset[4]) -local point=COORDINATE:NewFromVec3(vec3) -local coalition=tonumber(dataset[5]) -local country=tonumber(dataset[6]) -local description=dataset[7] -local typeName=dataset[8] -local unitName=dataset[9] -local freq=tonumber(dataset[10]) -self:_AddCsar(coalition,country,point,typeName,unitName,playerName,freq,nil,description,nil) -end -return self -end -AIRWING={ -ClassName="AIRWING", -verbose=0, -lid=nil, -menu=nil, -payloads={}, -payloadcounter=0, -pointsCAP={}, -pointsTANKER={}, -pointsAWACS={}, -pointsRecon={}, -markpoints=false, -capOptionPatrolRaceTrack=false, -capFormation=nil, -capOptionVaryStartTime=nil, -capOptionVaryEndTime=nil, -} -AIRWING.version="0.9.5" -function AIRWING:New(warehousename,airwingname) -local self=BASE:Inherit(self,LEGION:New(warehousename,airwingname)) -if not self then -BASE:E(string.format("ERROR: Could not find warehouse %s!",warehousename)) -return nil -end -self.lid=string.format("AIRWING %s | ",self.alias) -self.nflightsCAP=0 -self.nflightsAWACS=0 -self.nflightsRecon=0 -self.nflightsTANKERboom=0 -self.nflightsTANKERprobe=0 -self.nflightsRecoveryTanker=0 -self.nflightsRescueHelo=0 -self.markpoints=false -self:AddTransition("*","FlightOnMission","*") -return self -end -function AIRWING:AddSquadron(Squadron) -table.insert(self.cohorts,Squadron) -self:AddAssetToSquadron(Squadron,Squadron.Ngroups) -if Squadron.attribute==GROUP.Attribute.AIR_AWACS then -self:NewPayload(Squadron.templategroup,-1,AUFTRAG.Type.AWACS) -elseif Squadron.attribute==GROUP.Attribute.AIR_TANKER then -self:NewPayload(Squadron.templategroup,-1,AUFTRAG.Type.TANKER) -end -self:NewPayload(Squadron.templategroup,-1,AUFTRAG.Type.RELOCATECOHORT,0) -Squadron:SetAirwing(self) -if Squadron:IsStopped()then -Squadron:Start() -end -return self -end -function AIRWING:NewPayload(Unit,Npayloads,MissionTypes,Performance) -Performance=Performance or 50 -if type(Unit)=="string"then -local name=Unit -Unit=UNIT:FindByName(name) -if not Unit then -Unit=GROUP:FindByName(name) -end -end -if Unit then -if Unit:IsInstanceOf("GROUP")then -Unit=Unit:GetUnit(1) -end -if MissionTypes and type(MissionTypes)~="table"then -MissionTypes={MissionTypes} -end -local payload={} -payload.uid=self.payloadcounter -payload.unitname=Unit:GetName() -payload.aircrafttype=Unit:GetTypeName() -payload.pylons=Unit:GetTemplatePayload() -self:SetPayloadAmount(payload,Npayloads) -payload.capabilities={} -for _,missiontype in pairs(MissionTypes)do -local capability={} -capability.MissionType=missiontype -capability.Performance=Performance -table.insert(payload.capabilities,capability) -end -if not AUFTRAG.CheckMissionType(AUFTRAG.Type.ORBIT,MissionTypes)then -local capability={} -capability.MissionType=AUFTRAG.Type.ORBIT -capability.Performance=50 -table.insert(payload.capabilities,capability) -end -if not AUFTRAG.CheckMissionType(AUFTRAG.Type.RELOCATECOHORT,MissionTypes)then -local capability={} -capability.MissionType=AUFTRAG.Type.RELOCATECOHORT -capability.Performance=50 -table.insert(payload.capabilities,capability) -end -self:T(self.lid..string.format("Adding new payload from unit %s for aircraft type %s: ID=%d, N=%d (unlimited=%s), performance=%d, missions: %s", -payload.unitname,payload.aircrafttype,payload.uid,payload.navail,tostring(payload.unlimited),Performance,table.concat(MissionTypes,", "))) -table.insert(self.payloads,payload) -self.payloadcounter=self.payloadcounter+1 -return payload -end -self:E(self.lid.."ERROR: No UNIT found to create PAYLOAD!") -return nil -end -function AIRWING:SetPayloadAmount(Payload,Navailable) -Navailable=Navailable or 99 -if Payload then -Payload.unlimited=Navailable<0 -if Payload.unlimited then -Payload.navail=1 -else -Payload.navail=Navailable -end -end -return self -end -function AIRWING:IncreasePayloadAmount(Payload,N) -N=N or 1 -if Payload and Payload.navail>=0 then -Payload.navail=Payload.navail+N -Payload.navail=math.max(Payload.navail,0) -end -return self -end -function AIRWING:GetPayloadAmount(Payload) -return Payload.navail -end -function AIRWING:GetPayloadCapabilities(Payload) -return Payload.capabilities -end -function AIRWING:AddPayloadCapability(Payload,MissionTypes,Performance) -if MissionTypes and type(MissionTypes)~="table"then -MissionTypes={MissionTypes} -end -Payload.capabilities=Payload.capabilities or{} -for _,missiontype in pairs(MissionTypes)do -local capability={} -capability.MissionType=missiontype -capability.Performance=Performance -table.insert(Payload.capabilities,capability) -end -return self -end -function AIRWING:FetchPayloadFromStock(UnitType,MissionType,Payloads) -if not self.payloads or#self.payloads==0 then -self:T(self.lid.."WARNING: No payloads in stock!") -return nil -end -if self.verbose>=4 then -self:I(self.lid..string.format("Looking for payload for unit type=%s and mission type=%s",UnitType,MissionType)) -for i,_payload in pairs(self.payloads)do -local payload=_payload -local performance=self:GetPayloadPeformance(payload,MissionType) -self:I(self.lid..string.format("[%d] Payload type=%s navail=%d unlimited=%s",i,payload.aircrafttype,payload.navail,tostring(payload.unlimited))) -end -end -local function sortpayloads(a,b) -local pA=a -local pB=b -if a and b then -local performanceA=self:GetPayloadPeformance(a,MissionType) -local performanceB=self:GetPayloadPeformance(b,MissionType) -return(performanceA>performanceB)or(performanceA==performanceB and a.unlimited==true and b.unlimited~=true)or(performanceA==performanceB and a.unlimited==true and b.unlimited==true and a.navail>b.navail) -elseif not a then -self:I(self.lid..string.format("FF ERROR in sortpayloads: a is nil")) -return false -elseif not b then -self:I(self.lid..string.format("FF ERROR in sortpayloads: b is nil")) -return true -else -self:I(self.lid..string.format("FF ERROR in sortpayloads: a and b are nil")) -return false -end -end -local function _checkPayloads(payload) -if Payloads then -for _,Payload in pairs(Payloads)do -if Payload.uid==payload.uid then -return true -end -end -else -return nil -end -return false -end -local payloads={} -for _,_payload in pairs(self.payloads)do -local payload=_payload -local specialpayload=_checkPayloads(payload) -local compatible=AUFTRAG.CheckMissionCapability(MissionType,payload.capabilities) -local goforit=specialpayload or(specialpayload==nil and compatible) -if payload.aircrafttype==UnitType and payload.navail>0 and goforit then -table.insert(payloads,payload) -end -end -if self.verbose>=4 then -self:I(self.lid..string.format("Sorted payloads for mission type %s and aircraft type=%s:",MissionType,UnitType)) -for _,_payload in ipairs(self.payloads)do -local payload=_payload -if payload.aircrafttype==UnitType and AUFTRAG.CheckMissionCapability(MissionType,payload.capabilities)then -local performace=self:GetPayloadPeformance(payload,MissionType) -self:I(self.lid..string.format("- %s payload for %s: avail=%d performace=%d",MissionType,payload.aircrafttype,payload.navail,performace)) -end -end -end -if#payloads==0 then -self:T(self.lid..string.format("WARNING: Could not find a payload for airframe %s mission type %s!",UnitType,MissionType)) -return nil -elseif#payloads==1 then -local payload=payloads[1] -if not payload.unlimited then -payload.navail=payload.navail-1 -end -return payload -else -table.sort(payloads,sortpayloads) -local payload=payloads[1] -if not payload.unlimited then -payload.navail=payload.navail-1 -end -return payload -end -end -function AIRWING:ReturnPayloadFromAsset(asset) -local payload=asset.payload -if payload then -if not payload.unlimited then -payload.navail=payload.navail+1 -end -asset.payload=nil -else -self:E(self.lid.."ERROR: asset had no payload attached!") -end -end -function AIRWING:AddAssetToSquadron(Squadron,Nassets) -if Squadron then -local Group=GROUP:FindByName(Squadron.templatename) -if Group then -local text=string.format("Adding asset %s to squadron %s",Group:GetName(),Squadron.name) -self:T(self.lid..text) -self:AddAsset(Group,Nassets,nil,nil,nil,nil,Squadron.skill,Squadron.livery,Squadron.name) -else -self:E(self.lid.."ERROR: Group does not exist!") -end -else -self:E(self.lid.."ERROR: Squadron does not exit!") -end -return self -end -function AIRWING:GetSquadron(SquadronName) -local squad=self:_GetCohort(SquadronName) -return squad -end -function AIRWING:GetSquadronOfAsset(Asset) -return self:GetSquadron(Asset.squadname) -end -function AIRWING:RemoveAssetFromSquadron(Asset) -local squad=self:GetSquadronOfAsset(Asset) -if squad then -squad:DelAsset(Asset) -end -end -function AIRWING:SetNumberCAP(n) -self.nflightsCAP=n or 1 -return self -end -function AIRWING:SetCAPFormation(Formation) -self.capFormation=Formation -return self -end -function AIRWING:SetCapCloseRaceTrack(OnOff) -self.capOptionPatrolRaceTrack=OnOff -return self -end -function AIRWING:SetCapStartTimeVariation(Start,End) -self.capOptionVaryStartTime=Start or 5 -self.capOptionVaryEndTime=End or 60 -return self -end -function AIRWING:SetNumberTankerBoom(Nboom) -self.nflightsTANKERboom=Nboom or 1 -return self -end -function AIRWING:ShowPatrolPointMarkers(onoff) -if onoff then -self.markpoints=true -else -self.markpoints=false -end -return self -end -function AIRWING:SetNumberTankerProbe(Nprobe) -self.nflightsTANKERprobe=Nprobe or 1 -return self -end -function AIRWING:SetNumberAWACS(n) -self.nflightsAWACS=n or 1 -return self -end -function AIRWING:SetNumberRecon(n) -self.nflightsRecon=n or 1 -return self -end -function AIRWING:SetNumberRescuehelo(n) -self.nflightsRescueHelo=n or 1 -return self -end -function AIRWING:_PatrolPointMarkerText(point) -local text=string.format("%s Occupied=%d, \nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", -point.type,point.noccupied,point.heading,point.leg,point.altitude,point.speed) -return text -end -function AIRWING:UpdatePatrolPointMarker(point) -if self.markpoints then -local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", -point.type,point.noccupied,point.heading,point.leg,point.altitude,point.speed) -point.marker:UpdateText(text,1) -end -end -function AIRWING:NewPatrolPoint(Type,Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem) -if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE")then -Coordinate=Coordinate:GetCoordinate() -end -local patrolpoint={} -patrolpoint.type=Type or"Unknown" -patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10,15)),math.random(360)) -patrolpoint.heading=Heading or math.random(360) -patrolpoint.leg=LegLength or 15 -patrolpoint.altitude=Altitude or math.random(10,20)*1000 -patrolpoint.speed=Speed or 350 -patrolpoint.noccupied=0 -patrolpoint.refuelsystem=RefuelSystem -if self.markpoints then -patrolpoint.marker=MARKER:New(Coordinate,"New Patrol Point"):ToAll() -AIRWING.UpdatePatrolPointMarker(patrolpoint) -end -return patrolpoint -end -function AIRWING:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength) -local patrolpoint=self:NewPatrolPoint("CAP",Coordinate,Altitude,Speed,Heading,LegLength) -table.insert(self.pointsCAP,patrolpoint) -return self -end -function AIRWING:AddPatrolPointRecon(Coordinate,Altitude,Speed,Heading,LegLength) -local patrolpoint=self:NewPatrolPoint("RECON",Coordinate,Altitude,Speed,Heading,LegLength) -table.insert(self.pointsRecon,patrolpoint) -return self -end -function AIRWING:AddPatrolPointTANKER(Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem) -local patrolpoint=self:NewPatrolPoint("Tanker",Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem) -table.insert(self.pointsTANKER,patrolpoint) -return self -end -function AIRWING:AddPatrolPointAWACS(Coordinate,Altitude,Speed,Heading,LegLength) -local patrolpoint=self:NewPatrolPoint("AWACS",Coordinate,Altitude,Speed,Heading,LegLength) -table.insert(self.pointsAWACS,patrolpoint) -return self -end -function AIRWING:SetAirboss(airboss) -self.airboss=airboss -return self -end -function AIRWING:SetTakeoffType(TakeoffType) -TakeoffType=TakeoffType or"Cold" -if TakeoffType:lower()=="hot"then -self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot -elseif TakeoffType:lower()=="cold"then -self.takeoffType=COORDINATE.WaypointType.TakeOffParking -elseif TakeoffType:lower()=="air"then -self.takeoffType=COORDINATE.WaypointType.TurningPoint -else -self.takeoffType=COORDINATE.WaypointType.TakeOffParking -end -return self -end -function AIRWING:SetTakeoffCold() -self:SetTakeoffType("Cold") -return self -end -function AIRWING:SetTakeoffHot() -self:SetTakeoffType("Hot") -return self -end -function AIRWING:SetTakeoffAir() -self:SetTakeoffType("Air") -return self -end -function AIRWING:SetDespawnAfterLanding(Switch) -if Switch then -self.despawnAfterLanding=Switch -else -self.despawnAfterLanding=true -end -return self -end -function AIRWING:SetDespawnAfterHolding(Switch) -if Switch then -self.despawnAfterHolding=Switch -else -self.despawnAfterHolding=true -end -return self -end -function AIRWING:onafterStart(From,Event,To) -self:GetParent(self,AIRWING).onafterStart(self,From,Event,To) -self:I(self.lid..string.format("Starting AIRWING v%s",AIRWING.version)) -end -function AIRWING:onafterStatus(From,Event,To) -self:GetParent(self).onafterStatus(self,From,Event,To) -local fsmstate=self:GetState() -self:CheckCAP() -self:CheckTANKER() -self:CheckAWACS() -self:CheckRescuhelo() -self:CheckRECON() -self:CheckTransportQueue() -self:CheckMissionQueue() -if self.verbose>=1 then -local Nmissions=self:CountMissionsInQueue() -local Npayloads=self:CountPayloadsInStock(AUFTRAG.Type) -local Npq,Np,Nq=self:CountAssetsOnMission() -local assets=string.format("%d (OnMission: Total=%d, Active=%d, Queued=%d)",self:CountAssets(),Npq,Np,Nq) -local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d, Assets=%s",fsmstate,Nmissions,Npayloads,#self.payloads,#self.cohorts,assets) -self:I(self.lid..text) -end -if self.verbose>=2 then -local text=string.format("Missions Total=%d:",#self.missionqueue) -for i,_mission in pairs(self.missionqueue)do -local mission=_mission -local prio=string.format("%d/%s",mission.prio,tostring(mission.importance));if mission.urgent then prio=prio.." (!)"end -local assets=string.format("%d/%d",mission:CountOpsGroups(),mission:GetNumberOfRequiredAssets()) -local target=string.format("%d/%d Damage=%.1f",mission:CountMissionTargets(),mission:GetTargetInitialNumber(),mission:GetTargetDamage()) -local mystatus=mission:GetLegionStatus(self) -text=text..string.format("\n[%d] %s %s: Status=%s [%s], Prio=%s, Assets=%s, Targets=%s",i,mission.name,mission.type,mystatus,mission.status,prio,assets,target) -end -self:I(self.lid..text) -end -if self.verbose>=3 then -local text="Squadrons:" -for i,_squadron in pairs(self.cohorts)do -local squadron=_squadron -local callsign=squadron.callsignName and UTILS.GetCallsignName(squadron.callsignName)or"N/A" -local modex=squadron.modex and squadron.modex or-1 -local skill=squadron.skill and tostring(squadron.skill)or"N/A" -text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s",squadron.name,squadron:GetState(),squadron.aircrafttype,squadron:CountAssets(true),#squadron.assets,callsign,modex,skill) -end -self:I(self.lid..text) -end -end -function AIRWING:_GetPatrolData(PatrolPoints,RefuelSystem) -local function sort(a,b) -return a.noccupied0 then -table.sort(PatrolPoints,sort) -for _,_patrolpoint in pairs(PatrolPoints)do -local patrolpoint=_patrolpoint -if(RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem)or RefuelSystem==nil or patrolpoint.refuelsystem==nil then -return patrolpoint -end -end -end -return self:NewPatrolPoint() -end -function AIRWING:CheckCAP() -local Ncap=0 -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if mission:IsNotOver()and(mission.type==AUFTRAG.Type.GCICAP or mission.type==AUFTRAG.Type.PATROLRACETRACK)and mission.patroldata then -Ncap=Ncap+1 -end -end -for i=1,self.nflightsCAP-Ncap do -local patrol=self:_GetPatrolData(self.pointsCAP) -local altitude=patrol.altitude+1000*patrol.noccupied -local missionCAP=nil -if self.capOptionPatrolRaceTrack then -missionCAP=AUFTRAG:NewPATROL_RACETRACK(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg,self.capFormation) -else -missionCAP=AUFTRAG:NewGCICAP(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg) -end -if self.capOptionVaryStartTime then -local ClockStart=math.random(self.capOptionVaryStartTime,self.capOptionVaryEndTime) -missionCAP:SetTime(ClockStart) -end -missionCAP.patroldata=patrol -patrol.noccupied=patrol.noccupied+1 -if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end -self:AddMission(missionCAP) -end -return self -end -function AIRWING:CheckRECON() -local Ncap=0 -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if mission:IsNotOver()and mission.type==AUFTRAG.Type.RECON and mission.patroldata then -Ncap=Ncap+1 -end -end -for i=1,self.nflightsRecon-Ncap do -local patrol=self:_GetPatrolData(self.pointsRecon) -local altitude=patrol.altitude -local ZoneSet=SET_ZONE:New() -local Zone=ZONE_RADIUS:New(self.alias.." Recon "..math.random(1,10000),patrol.coord:GetVec2(),UTILS.NMToMeters(patrol.leg/2)) -ZoneSet:AddZone(Zone) -if self.Debug then -Zone:DrawZone(self.coalition,{0,0,1},Alpha,FillColor,FillAlpha,2,true) -end -local missionRECON=AUFTRAG:NewRECON(ZoneSet,patrol.speed,patrol.altitude,true) -missionRECON.patroldata=patrol -missionRECON.categories={AUFTRAG.Category.AIRCRAFT} -patrol.noccupied=patrol.noccupied+1 -if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end -self:AddMission(missionRECON) -end -return self -end -function AIRWING:CheckTANKER() -local Nboom=0 -local Nprob=0 -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if mission:IsNotOver()and mission.type==AUFTRAG.Type.TANKER and mission.patroldata then -if mission.refuelSystem==Unit.RefuelingSystem.BOOM_AND_RECEPTACLE then -Nboom=Nboom+1 -elseif mission.refuelSystem==Unit.RefuelingSystem.PROBE_AND_DROGUE then -Nprob=Nprob+1 -end -end -end -for i=1,self.nflightsTANKERboom-Nboom do -local patrol=self:_GetPatrolData(self.pointsTANKER) -local altitude=patrol.altitude+1000*patrol.noccupied -local mission=AUFTRAG:NewTANKER(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg,Unit.RefuelingSystem.BOOM_AND_RECEPTACLE) -mission.patroldata=patrol -patrol.noccupied=patrol.noccupied+1 -if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end -self:AddMission(mission) -end -for i=1,self.nflightsTANKERprobe-Nprob do -local patrol=self:_GetPatrolData(self.pointsTANKER) -local altitude=patrol.altitude+1000*patrol.noccupied -local mission=AUFTRAG:NewTANKER(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg,Unit.RefuelingSystem.PROBE_AND_DROGUE) -mission.patroldata=patrol -patrol.noccupied=patrol.noccupied+1 -if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end -self:AddMission(mission) -end -return self -end -function AIRWING:CheckAWACS() -local N=0 -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if mission:IsNotOver()and mission.type==AUFTRAG.Type.AWACS and mission.patroldata then -N=N+1 -end -end -for i=1,self.nflightsAWACS-N do -local patrol=self:_GetPatrolData(self.pointsAWACS) -local altitude=patrol.altitude+1000*patrol.noccupied -local mission=AUFTRAG:NewAWACS(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg) -mission.patroldata=patrol -patrol.noccupied=patrol.noccupied+1 -if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end -self:AddMission(mission) -end -return self -end -function AIRWING:CheckRescuhelo() -local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO}) -local name=self.airbase:GetName() -local carrier=UNIT:FindByName(name) -for i=1,self.nflightsRescueHelo-N do -local mission=AUFTRAG:NewRESCUEHELO(carrier) -self:AddMission(mission) -end -return self -end -function AIRWING:GetTankerForFlight(flightgroup) -local tankers=self:GetAssetsOnMission(AUFTRAG.Type.TANKER) -if#tankers>0 then -local tankeropt={} -for _,_tanker in pairs(tankers)do -local tanker=_tanker -if flightgroup.refueltype and flightgroup.refueltype==tanker.flightgroup.tankertype then -local tankercoord=tanker.flightgroup.group:GetCoordinate() -local assetcoord=flightgroup.group:GetCoordinate() -local dist=assetcoord:Get2DDistance(tankercoord) -if dist>5 then -table.insert(tankeropt,{tanker=tanker,dist=dist}) -end -end -end -table.sort(tankeropt,function(a,b)return a.dist0 then -return tankeropt[1].tanker -else -return nil -end -end -return nil -end -function AIRWING:SetUsingOpsAwacs(ConnectecdAwacs) -self:I(self.lid.."Added AWACS Object: "..ConnectecdAwacs:GetName()or"unknown") -self.UseConnectedOpsAwacs=true -self.ConnectedOpsAwacs=ConnectecdAwacs -return self -end -function AIRWING:RemoveUsingOpsAwacs() -self:I(self.lid.."Reomve AWACS Object: "..self.ConnectedOpsAwacs:GetName()or"unknown") -self.UseConnectedOpsAwacs=false -return self -end -function AIRWING:onafterFlightOnMission(From,Event,To,FlightGroup,Mission) -self:T(self.lid..string.format("Group %s on %s mission %s",FlightGroup:GetName(),Mission:GetType(),Mission:GetName())) -if self.UseConnectedOpsAwacs and self.ConnectedOpsAwacs then -self.ConnectedOpsAwacs:__FlightOnMission(2,FlightGroup,Mission) -end -end -function AIRWING:CountPayloadsInStock(MissionTypes,UnitTypes,Payloads) -if MissionTypes then -if type(MissionTypes)=="string"then -MissionTypes={MissionTypes} -end -end -if UnitTypes then -if type(UnitTypes)=="string"then -UnitTypes={UnitTypes} -end -end -local function _checkUnitTypes(payload) -if UnitTypes then -for _,unittype in pairs(UnitTypes)do -if unittype==payload.aircrafttype then -return true -end -end -else -return true -end -return false -end -local function _checkPayloads(payload) -if Payloads then -for _,Payload in pairs(Payloads)do -if Payload.uid==payload.uid then -return true -end -end -else -return nil -end -return false -end -local n=0 -for _,_payload in pairs(self.payloads)do -local payload=_payload -for _,MissionType in pairs(MissionTypes)do -local specialpayload=_checkPayloads(payload) -local compatible=AUFTRAG.CheckMissionCapability(MissionType,payload.capabilities) -local goforit=specialpayload or(specialpayload==nil and compatible) -if goforit and _checkUnitTypes(payload)then -if payload.unlimited then -return 999 -else -n=n+payload.navail -end -end -end -end -return n -end -function AIRWING:GetPayloadPeformance(Payload,MissionType) -if Payload then -for _,Capability in pairs(Payload.capabilities)do -local capability=Capability -if capability.MissionType==MissionType then -return capability.Performance -end -end -else -self:E(self.lid.."ERROR: Payload is nil!") -end -return-1 -end -function AIRWING:GetPayloadMissionTypes(Payload) -local missiontypes={} -for _,Capability in pairs(Payload.capabilities)do -local capability=Capability -table.insert(missiontypes,capability.MissionType) -end -return missiontypes -end -ARMYGROUP={ -ClassName="ARMYGROUP", -formationPerma=nil, -engage={}, -} -ARMYGROUP.version="1.0.1" -function ARMYGROUP:New(group) -local og=_DATABASE:GetOpsGroup(group) -if og then -og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) -return og -end -local self=BASE:Inherit(self,OPSGROUP:New(group)) -self.lid=string.format("ARMYGROUP %s | ",self.groupname) -self:SetDefaultROE() -self:SetDefaultAlarmstate() -self:SetDefaultEPLRS(self.isEPLRS) -self:SetDefaultEmission() -self:SetDetection() -self:SetPatrolAdInfinitum(false) -self:SetRetreatZones() -self:AddTransition("*","FullStop","Holding") -self:AddTransition("*","Cruise","Cruising") -self:AddTransition("*","RTZ","Returning") -self:AddTransition("Holding","Returned","Returned") -self:AddTransition("Returning","Returned","Returned") -self:AddTransition("*","Detour","OnDetour") -self:AddTransition("OnDetour","DetourReached","Cruising") -self:AddTransition("*","Retreat","Retreating") -self:AddTransition("Retreating","Retreated","Retreated") -self:AddTransition("*","Suppressed","*") -self:AddTransition("*","Unsuppressed","*") -self:AddTransition("Cruising","EngageTarget","Engaging") -self:AddTransition("Holding","EngageTarget","Engaging") -self:AddTransition("OnDetour","EngageTarget","Engaging") -self:AddTransition("Engaging","Disengage","Cruising") -self:AddTransition("*","Rearm","Rearm") -self:AddTransition("Rearm","Rearming","Rearming") -self:AddTransition("*","Rearmed","Cruising") -self:_InitWaypoints() -self:_InitGroup() -self:HandleEvent(EVENTS.Birth,self.OnEventBirth) -self:HandleEvent(EVENTS.Dead,self.OnEventDead) -self:HandleEvent(EVENTS.RemoveUnit,self.OnEventRemoveUnit) -self:HandleEvent(EVENTS.Hit,self.OnEventHit) -self.timerStatus=TIMER:New(self.Status,self):Start(1,30) -self.timerQueueUpdate=TIMER:New(self._QueueUpdate,self):Start(2,5) -self.timerCheckZone=TIMER:New(self._CheckInZones,self):Start(2,30) -_DATABASE:AddOpsGroup(self) -return self -end -function ARMYGROUP:SetPatrolAdInfinitum(switch) -if switch==false then -self.adinfinitum=false -else -self.adinfinitum=true -end -return self -end -function ARMYGROUP:GetClosestRoad() -local coord=self:GetCoordinate():GetClosestPointToRoad() -return coord -end -function ARMYGROUP:GetClosestRoadDist() -local road=self:GetClosestRoad() -if road then -local dist=road:Get2DDistance(self:GetCoordinate()) -return dist -end -return math.huge -end -function ARMYGROUP:AddTaskFireAtPoint(Coordinate,Clock,Radius,Nshots,WeaponType,Prio) -Coordinate=self:_CoordinateFromObject(Coordinate) -local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,Coordinate:GetVec2(),Radius,Nshots,WeaponType) -local task=self:AddTask(DCStask,Clock,nil,Prio) -return task -end -function ARMYGROUP:AddTaskBarrage(Clock,Heading,Alpha,Altitude,Radius,Nshots,WeaponType,Prio) -Heading=Heading or 0 -Alpha=Alpha or 60 -Altitude=Altitude or 100 -local distance=Altitude/math.tan(math.rad(Alpha)) -local a=self:GetVec2() -local vec2=UTILS.Vec2Translate(a,distance,Heading) -local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,vec2,Radius,Nshots,WeaponType,Altitude) -local task=self:AddTask(DCStask,Clock,nil,Prio) -return task -end -function ARMYGROUP:AddTaskWaypointFireAtPoint(Coordinate,Waypoint,Radius,Nshots,WeaponType,Prio) -Coordinate=self:_CoordinateFromObject(Coordinate) -Waypoint=Waypoint or self:GetWaypointNext() -local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,Coordinate:GetVec2(),Radius,Nshots,WeaponType) -local task=self:AddTaskWaypoint(DCStask,Waypoint,nil,Prio) -return task -end -function ARMYGROUP:AddTaskAttackGroup(TargetGroup,WeaponExpend,WeaponType,Clock,Prio) -local DCStask=CONTROLLABLE.TaskAttackGroup(nil,TargetGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit,GroupAttack) -local task=self:AddTask(DCStask,Clock,nil,Prio) -return task -end -function ARMYGROUP:AddTaskCargoGroup(GroupSet,PickupZone,DeployZone,Clock,Prio) -local DCStask={} -DCStask.id="CargoTransport" -DCStask.params={} -DCStask.params.cargoqueu=1 -local task=self:AddTask(DCStask,Clock,nil,Prio) -return task -end -function ARMYGROUP:SetRetreatZones(RetreatZoneSet) -self.retreatZones=RetreatZoneSet or SET_ZONE:New() -return self -end -function ARMYGROUP:AddRetreatZone(RetreatZone) -self.retreatZones:AddZone(RetreatZone) -return self -end -function ARMYGROUP:SetSuppressionOn(Tave,Tmin,Tmax) -self.suppressionOn=true -self.TsuppressMin=Tmin or 1 -self.TsuppressMin=math.max(self.TsuppressMin,1) -self.TsuppressMax=Tmax or 15 -self.TsuppressMax=math.max(self.TsuppressMax,self.TsuppressMin) -self.TsuppressAve=Tave or 10 -self.TsuppressAve=math.max(self.TsuppressMin) -self.TsuppressAve=math.min(self.TsuppressMax) -self:T(self.lid..string.format("Set ave suppression time to %d seconds.",self.TsuppressAve)) -self:T(self.lid..string.format("Set min suppression time to %d seconds.",self.TsuppressMin)) -self:T(self.lid..string.format("Set max suppression time to %d seconds.",self.TsuppressMax)) -return self -end -function ARMYGROUP:SetSuppressionOff() -self.suppressionOn=false -end -function ARMYGROUP:IsHolding() -return self:Is("Holding") -end -function ARMYGROUP:IsCruising() -return self:Is("Cruising") -end -function ARMYGROUP:IsOnDetour() -return self:Is("OnDetour") -end -function ARMYGROUP:IsCombatReady() -local combatready=true -if self:IsRearming()or self:IsRetreating()or self:IsOutOfAmmo()or self:IsEngaging()or self:IsDead()or self:IsStopped()or self:IsInUtero()then -combatready=false -end -if self:IsPickingup()or self:IsLoading()or self:IsTransporting()or self:IsLoaded()or self:IsCargo()or self:IsCarrier()then -combatready=false -end -return combatready -end -function ARMYGROUP:Status() -local fsmstate=self:GetState() -local alive=self:IsAlive() -if alive then -self:_UpdatePosition() -self:_CheckDetectedUnits() -self:_CheckAmmoStatus() -self:_CheckDamage() -self:_CheckStuck() -if self:IsEngaging()then -self:_UpdateEngageTarget() -end -if self:IsWaiting()then -if self.Twaiting and self.dTwait then -if timer.getAbsTime()>self.Twaiting+self.dTwait then -self.Twaiting=nil -self.dTwait=nil -if self:_CountPausedMissions()>0 then -self:UnpauseMission() -else -self:Cruise() -end -end -end -end -local mission=self:GetMissionCurrent() -if mission and mission.updateDCSTask then -if mission.type==AUFTRAG.Type.CAPTUREZONE then -local Task=mission:GetGroupWaypointTask(self) -if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING or mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.STARTED then -self:_UpdateTask(Task,mission) -end -end -end -else -self:_CheckDamage() -end -if alive~=nil then -if self.verbose>=1 then -local nelem=self:CountElements() -local Nelem=#self.elements -local nTaskTot,nTaskSched,nTaskWP=self:CountRemainingTasks() -local nMissions=self:CountRemainingMissison() -local roe=self:GetROE()or-1 -local als=self:GetAlarmstate()or-1 -local wpidxCurr=self.currentwp -local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr)or 0 -local wpidxNext=self:GetWaypointIndexNext()or 0 -local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext)or 0 -local wpN=#self.waypoints or 0 -local wpF=tostring(self.passedfinalwp) -local speed=UTILS.MpsToKnots(self.velocity or 0) -local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) -local alt=self.position and self.position.y or 0 -local hdg=self.heading or 0 -local formation=self.option.Formation or"unknown" -local life=self.life or 0 -local ammo=self:GetAmmoTot().Total -local ndetected=self.detectionOn and tostring(self.detectedunits:Count())or"Off" -local cargo=0 -for _,_element in pairs(self.elements)do -local element=_element -cargo=cargo+element.weightCargo -end -local text=string.format("%s [%d/%d]: ROE/AS=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) [%s] | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f", -fsmstate,nelem,Nelem,roe,als,nTaskTot,nMissions,wpidxCurr,wpuidCurr,wpidxNext,wpuidNext,wpN,wpF,life,speed,speedEx,formation,hdg,ammo,ndetected,cargo) -self:I(self.lid..text) -end -else -if self.verbose>=1 then -local text=string.format("State %s: Alive=%s",fsmstate,tostring(self:IsAlive())) -self:I(self.lid..text) -end -end -if self.verbose>=2 then -local text="Elements:" -for i,_element in pairs(self.elements)do -local element=_element -local name=element.name -local status=element.status -local unit=element.unit -local life,life0=self:GetLifePoints(element) -local life0=element.life0 -local ammo=self:GetAmmoElement(element) -text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg", -i,name,status,life,life0,ammo.Guns,ammo.Rockets,ammo.Bombs,ammo.Missiles,element.weightCargo,element.weightMaxCargo) -end -if#self.elements==0 then -text=text.." none!" -end -self:T(self.lid..text) -end -if self:IsCruising()and self.detectionOn and self.engagedetectedOn then -local targetgroup,targetdist=self:_GetDetectedTarget() -if targetgroup then -self:T(self.lid..string.format("Engaging target group %s at distance %d meters",targetgroup:GetName(),targetdist)) -self:EngageTarget(targetgroup) -end -end -self:_CheckCargoTransport() -self:_PrintTaskAndMissionStatus() -end -function ARMYGROUP:onafterElementSpawned(From,Event,To,Element) -self:T(self.lid..string.format("Element spawned %s",Element.name)) -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.SPAWNED) -end -function ARMYGROUP:onafterSpawned(From,Event,To) -self:T(self.lid..string.format("Group spawned!")) -if self.verbose>=1 then -local text=string.format("Initialized Army Group %s:\n",self.groupname) -text=text..string.format("Unit type = %s\n",self.actype) -text=text..string.format("Speed max = %.1f Knots\n",UTILS.KmphToKnots(self.speedMax)) -text=text..string.format("Speed cruise = %.1f Knots\n",UTILS.KmphToKnots(self.speedCruise)) -text=text..string.format("Weight = %.1f kg\n",self:GetWeightTotal()) -text=text..string.format("Cargo bay = %.1f kg\n",self:GetFreeCargobay()) -text=text..string.format("Has EPLRS = %s\n",tostring(self.isEPLRS)) -text=text..string.format("Elements = %d\n",#self.elements) -text=text..string.format("Waypoints = %d\n",#self.waypoints) -text=text..string.format("Radio = %.1f MHz %s %s\n",self.radio.Freq,UTILS.GetModulationName(self.radio.Modu),tostring(self.radio.On)) -text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Rockets,self.ammo.Missiles) -text=text..string.format("FSM state = %s\n",self:GetState()) -text=text..string.format("Is alive = %s\n",tostring(self:IsAlive())) -text=text..string.format("LateActivate = %s\n",tostring(self:IsLateActivated())) -self:I(self.lid..text) -end -self:_UpdatePosition() -self.isDead=false -self.isDestroyed=false -if self.isAI then -self:SwitchROE(self.option.ROE) -self:SwitchAlarmstate(self.option.Alarm) -self:SwitchEmission(self.option.Emission) -self:SwitchEPLRS(self.option.EPLRS) -self:SwitchInvisible(self.option.Invisible) -self:SwitchImmortal(self.option.Immortal) -self:_SwitchTACAN() -if self.radioDefault then -self:SwitchRadio(self.radioDefault.Freq,self.radioDefault.Modu) -else -self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,true) -end -if not self.option.Formation then -end -local Nwp=#self.waypoints -if Nwp>1 and self.isMobile then -self:T(self.lid..string.format("Got %d waypoints on spawn ==> Cruise in -1.0 sec!",Nwp)) -self:__Cruise(-1,nil,self.option.Formation) -else -self:T(self.lid.."No waypoints on spawn ==> Full Stop!") -self:FullStop() -end -end -end -function ARMYGROUP:onbeforeUpdateRoute(From,Event,To,n,N,Speed,Formation) -local allowed=true -local trepeat=nil -if self:IsWaiting()then -self:T(self.lid.."Update route denied. Group is WAITING!") -return false -elseif self:IsInUtero()then -self:T(self.lid.."Update route denied. Group is INUTERO!") -return false -elseif self:IsDead()then -self:T(self.lid.."Update route denied. Group is DEAD!") -return false -elseif self:IsStopped()then -self:T(self.lid.."Update route denied. Group is STOPPED!") -return false -elseif self:IsHolding()then -self:T(self.lid.."Update route denied. Group is holding position!") -return false -elseif self:IsEngaging()then -self:T(self.lid.."Update route allowed. Group is engaging!") -return true -end -if self.taskcurrent>0 then -local task=self:GetTaskByID(self.taskcurrent) -if task then -if task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then -self:T2(self.lid.."Allowing update route for Task: PatrolZone") -elseif task.dcstask.id==AUFTRAG.SpecialTask.RECON then -self:T2(self.lid.."Allowing update route for Task: ReconMission") -elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then -self:T2(self.lid.."Allowing update route for Task: Relocate Cohort") -elseif task.dcstask.id==AUFTRAG.SpecialTask.REARMING then -self:T2(self.lid.."Allowing update route for Task: Rearming") -else -local taskname=task and task.description or"No description" -self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s",self.taskcurrent,tostring(taskname))) -allowed=false -end -else -self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d (>0!) but no task?!",self.taskcurrent)) -allowed=false -end -end -if not self.isAI then -allowed=false -end -self:T2(self.lid..string.format("Onbefore Updateroute in state %s: allowed=%s (repeat in %s)",self:GetState(),tostring(allowed),tostring(trepeat))) -if trepeat then -self:__UpdateRoute(trepeat,n) -end -return allowed -end -function ARMYGROUP:onafterUpdateRoute(From,Event,To,n,N,Speed,Formation) -n=n or self:GetWaypointIndexNext(self.adinfinitum) -N=N or#self.waypoints -N=math.min(N,#self.waypoints) -local text=string.format("Update route state=%s: n=%s, N=%s, Speed=%s, Formation=%s",self:GetState(),tostring(n),tostring(N),tostring(Speed),tostring(Formation)) -self:T(self.lid..text) -local waypoints={} -local wp=self.waypoints[n] -local coordinate=self:GetCoordinate() -local coordRoad=coordinate:GetClosestPointToRoad() -local roaddist=coordinate:Get2DDistance(coordRoad) -local formation0=wp.action -if formation0==ENUMS.Formation.Vehicle.OnRoad then -if roaddist>10 then -formation0=ENUMS.Formation.Vehicle.OffRoad -else -formation0=ENUMS.Formation.Vehicle.OnRoad -end -end -local current=coordinate:WaypointGround(UTILS.MpsToKmph(self.speedWp),formation0) -table.insert(waypoints,1,current) -if N-n>0 then -for j=n,N do -local i=j-1 -if i==0 then -i=self.currentwp -end -local wp=UTILS.DeepCopy(self.waypoints[j]) -local wp0=self.waypoints[i] -if false and self.attribute==GROUP.Attribute.GROUND_APC then -local text=string.format("FF Update: i=%d, wp[i]=%s, wp[i-1]=%s",i,wp.action,wp0.action) -env.info(text) -end -if Speed then -wp.speed=UTILS.KnotsToMps(tonumber(Speed)) -else -if wp.speed<0.1 then -wp.speed=UTILS.KmphToMps(self.speedCruise) -end -end -if self.formationPerma then -wp.action=self.formationPerma -elseif Formation then -wp.action=Formation -end -if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp0.roaddist>=0 then -local wproad=wp0.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed),ENUMS.Formation.Vehicle.OnRoad) -table.insert(waypoints,wproad) -end -if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>=0 then -wp.action=ENUMS.Formation.Vehicle.OffRoad -local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed),ENUMS.Formation.Vehicle.OnRoad) -table.insert(waypoints,wproad) -end -table.insert(waypoints,wp) -end -else -local wp=UTILS.DeepCopy(self.waypoints[n]) -if wp.speed<0.1 then -wp.speed=UTILS.KmphToMps(self.speedCruise) -end -local formation=wp.action -if self.formationPerma then -formation=self.formationPerma -elseif Formation then -formation=Formation -end -if formation==ENUMS.Formation.Vehicle.OnRoad then -if roaddist>10 then -local wproad=coordRoad:WaypointGround(UTILS.MpsToKmph(wp.speed),ENUMS.Formation.Vehicle.OnRoad) -table.insert(waypoints,wproad) -end -if wp.roaddist>10 then -local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed),ENUMS.Formation.Vehicle.OnRoad) -table.insert(waypoints,wproad) -end -end -if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>10 then -wp.action=ENUMS.Formation.Vehicle.OffRoad -end -table.insert(waypoints,wp) -end -local wp=waypoints[1] -self.option.Formation=wp.action -self.speedWp=wp.speed -if self.verbose>=10 then -for i,_wp in pairs(waypoints)do -local wp=_wp -local text=string.format("WP #%d UID=%d Formation=%s: Speed=%d m/s, Alt=%d m, Type=%s",i,wp.uid and wp.uid or-1,wp.action,wp.speed,wp.alt,wp.type) -local coord=COORDINATE:NewFromWaypoint(wp):MarkToAll(text) -self:I(text) -end -end -if self:IsEngaging()or not self.passedfinalwp then -self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Formation=%s", -self.currentwp,n,#waypoints,#self.waypoints,UTILS.MpsToKnots(self.speedWp),tostring(self.option.Formation))) -self:Route(waypoints) -else -self:T(self.lid..string.format("WARNING: Passed final WP when UpdateRoute() ==> Full Stop!")) -self:FullStop() -end -end -function ARMYGROUP:onafterGotoWaypoint(From,Event,To,UID,Speed,Formation) -local n=self:GetWaypointIndex(UID) -if n then -Speed=Speed or self:GetSpeedToWaypoint(n) -self:__UpdateRoute(-0.01,n,nil,Speed,Formation) -end -end -function ARMYGROUP:onafterDetour(From,Event,To,Coordinate,Speed,Formation,ResumeRoute) -for _,_wp in pairs(self.waypoints)do -local wp=_wp -if wp.detour then -self:RemoveWaypointByID(wp.uid) -end -end -Speed=Speed or self:GetSpeedCruise() -local uid=self:GetWaypointCurrentUID() -local wp=self:AddWaypoint(Coordinate,Speed,uid,Formation,true) -if ResumeRoute then -wp.detour=1 -else -wp.detour=0 -end -end -function ARMYGROUP:onafterOutOfAmmo(From,Event,To) -self:T(self.lid..string.format("Group is out of ammo at t=%.3f",timer.getTime())) -local task=self:GetTaskCurrent() -if task then -if task.dcstask.id=="FireAtPoint"or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then -self:T(self.lid..string.format("Cancelling current %s task because out of ammo!",task.dcstask.id)) -self:TaskCancel(task) -end -end -if self.rearmOnOutOfAmmo then -local truck,dist=self:FindNearestAmmoSupply(30) -if truck then -self:T(self.lid..string.format("Found Ammo Truck %s [%s]",truck:GetName(),truck:GetTypeName())) -local Coordinate=truck:GetCoordinate() -self:__Rearm(-1,Coordinate) -return -end -end -if self.retreatOnOutOfAmmo then -self:T(self.lid.."Retreat on out of ammo") -self:__Retreat(-1) -return -end -if self.rtzOnOutOfAmmo and not self:IsMissionTypeInQueue(AUFTRAG.Type.REARMING)then -self:T(self.lid.."RTZ on out of ammo") -self:__RTZ(-1) -end -end -function ARMYGROUP:onbeforeRearm(From,Event,To,Coordinate,Formation) -local dt=nil -local allowed=true -if self:IsOnMission()then -local mission=self:GetMissionCurrent() -if mission and mission.type~=AUFTRAG.Type.REARMING then -self:T(self.lid.."Rearm command but have current mission ==> Pausing mission!") -self:PauseMission() -dt=-0.1 -allowed=false -else -self:T(self.lid.."Rearm command and current mission is REARMING ==> Transition ALLOWED!") -end -end -if self:IsEngaging()then -self:T(self.lid.."Rearm command but currently engaging ==> Disengage!") -self:Disengage() -dt=-0.1 -allowed=false -end -if allowed and not Coordinate then -local truck=self:FindNearestAmmoSupply() -if truck and truck:IsAlive()then -self:__Rearm(-0.1,truck:GetCoordinate(),Formation) -end -return false -end -if dt then -self:T(self.lid..string.format("Trying Rearm again in %.2f sec",dt)) -self:__Rearm(dt,Coordinate,Formation) -allowed=false -end -return allowed -end -function ARMYGROUP:onafterRearm(From,Event,To,Coordinate,Formation) -self:T(self.lid..string.format("Group send to rearm")) -local uid=self:GetWaypointCurrentUID() -local wp=self:AddWaypoint(Coordinate,nil,uid,Formation,true) -wp.detour=0 -end -function ARMYGROUP:onafterRearmed(From,Event,To) -self:T(self.lid.."Group rearmed") -local mission=self:GetMissionCurrent() -if mission and mission.type==AUFTRAG.Type.REARMING then -self:MissionDone(mission) -else -self:_CheckGroupDone(1) -end -end -function ARMYGROUP:onbeforeRTZ(From,Event,To,Zone,Formation) -self:T2(self.lid.."onbeforeRTZ") -local zone=Zone or self.homezone -if zone then -if(not self.isMobile)and(not self:IsInZone(zone))then -self:Teleport(zone:GetCoordinate(),0,true) -self:__RTZ(-1,Zone,Formation) -return false -end -else -return false -end -return true -end -function ARMYGROUP:onafterRTZ(From,Event,To,Zone,Formation) -self:T2(self.lid.."onafterRTZ") -local zone=Zone or self.homezone -self:CancelAllMissions() -if zone then -if self:IsInZone(zone)then -self:Returned() -else -self:T(self.lid..string.format("RTZ to Zone %s",zone:GetName())) -local Coordinate=zone:GetRandomCoordinate() -local uid=self:GetWaypointCurrentUID() -local wp=self:AddWaypoint(Coordinate,nil,uid,Formation,true) -wp.detour=0 -end -else -self:T(self.lid.."ERROR: No RTZ zone given!") -end -end -function ARMYGROUP:onafterReturned(From,Event,To) -self:T(self.lid..string.format("Group returned")) -if self.legion then -self:T(self.lid..string.format("Adding group back to warehouse stock")) -self.legion:__AddAsset(10,self.group,1) -end -end -function ARMYGROUP:onafterRearming(From,Event,To) -local pos=self:GetCoordinate() -local wp=pos:WaypointGround(0) -self:Route({wp}) -end -function ARMYGROUP:onbeforeRetreat(From,Event,To,Zone,Formation) -if not Zone then -local a=self:GetVec2() -local distmin=math.huge -local zonemin=nil -for _,_zone in pairs(self.retreatZones:GetSet())do -local zone=_zone -local b=zone:GetVec2() -local dist=UTILS.VecDist2D(a,b) -if dist Pausing mission!") -self:PauseMission() -dt=-0.1 -allowed=false -end -if dt then -self:T(self.lid..string.format("Trying Engage again in %.2f sec",dt)) -self:__EngageTarget(dt,Target) -allowed=false -end -return allowed -end -function ARMYGROUP:onafterEngageTarget(From,Event,To,Target,Speed,Formation) -self:T(self.lid.."Engaging Target") -if Target:IsInstanceOf("TARGET")then -self.engage.Target=Target -else -self.engage.Target=TARGET:New(Target) -end -self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) -local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.95) -self.engage.roe=self:GetROE() -self.engage.alarmstate=self:GetAlarmstate() -self:SwitchAlarmstate(ENUMS.AlarmState.Auto) -self:SwitchROE(ENUMS.ROE.OpenFire) -local uid=self:GetWaypointCurrentUID() -self.engage.Formation=Formation or ENUMS.Formation.Vehicle.Vee -self.engage.Speed=Speed -self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true) -self.engage.Waypoint.detour=1 -end -function ARMYGROUP:_UpdateEngageTarget() -if self.engage.Target and self.engage.Target:IsAlive()then -local vec3=self.engage.Target:GetVec3() -if vec3 then -local dist=UTILS.VecDist3D(vec3,self.engage.Coordinate:GetVec3()) -local los=self:HasLoS(vec3) -if dist>100 or los==false then -self.engage.Coordinate:UpdateFromVec3(vec3) -local uid=self:GetWaypointCurrentUID() -self:RemoveWaypointByID(self.engage.Waypoint.uid) -local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.9) -self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true) -self.engage.Waypoint.detour=0 -end -else -self:T(self.lid.."Could not get position of target ==> Disengage!") -self:Disengage() -end -else -self:T(self.lid.."Target not ALIVE ==> Disengage!") -self:Disengage() -end -end -function ARMYGROUP:onafterDisengage(From,Event,To) -self:T(self.lid.."Disengage Target") -self:SwitchROE(self.engage.roe) -self:SwitchAlarmstate(self.engage.alarmstate) -local task=self:GetTaskCurrent() -if task and task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK then -self:T(self.lid.."Disengage with current task GROUNDATTACK ==> Task Done!") -self:TaskDone(task) -end -if self.engage.Waypoint then -self:RemoveWaypointByID(self.engage.Waypoint.uid) -end -self:_CheckGroupDone(1) -end -function ARMYGROUP:onafterDetourReached(From,Event,To) -self:T(self.lid.."Group reached detour coordinate") -end -function ARMYGROUP:onafterFullStop(From,Event,To) -self:T(self.lid..string.format("Full stop!")) -local pos=self:GetCoordinate() -local wp=pos:WaypointGround(0) -self:Route({wp}) -end -function ARMYGROUP:onafterCruise(From,Event,To,Speed,Formation) -self.Twaiting=nil -self.dTwait=nil -self:T(self.lid.."Cruise ==> Update route in 0.01 sec") -self:__UpdateRoute(-0.01,nil,nil,Speed,Formation) -end -function ARMYGROUP:onafterHit(From,Event,To,Enemy) -self:T(self.lid..string.format("ArmyGroup hit by %s",Enemy and Enemy:GetName()or"unknown")) -if self.suppressionOn then -env.info(self.lid.."FF suppress") -self:_Suppress() -end -end -function ARMYGROUP:AddWaypoint(Coordinate,Speed,AfterWaypointWithID,Formation,Updateroute) -self:T(self.lid..string.format("AddWaypoint Formation = %s",tostring(Formation))) -local coordinate=self:_CoordinateFromObject(Coordinate) -local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) -Speed=Speed or self:GetSpeedCruise() -if not Formation then -if self.formationPerma then -Formation=self.formationPerma -elseif self.optionDefault.Formation then -Formation=self.optionDefault.Formation -elseif self.option.Formation then -Formation=self.option.Formation -else -Formation=ENUMS.Formation.Vehicle.OnRoad -end -self:T2(self.lid..string.format("Formation set to = %s",tostring(Formation))) -end -local wp=coordinate:WaypointGround(UTILS.KnotsToKmph(Speed),Formation) -local waypoint=self:_CreateWaypoint(wp) -self:_AddWaypoint(waypoint,wpnumber) -waypoint.roadcoord=coordinate:GetClosestPointToRoad(false) -if waypoint.roadcoord then -waypoint.roaddist=coordinate:Get2DDistance(waypoint.roadcoord) -else -waypoint.roaddist=1000*1000 -end -self:T(self.lid..string.format("Adding waypoint UID=%d (index=%d), Speed=%.1f knots, Dist2Road=%d m, Action=%s",waypoint.uid,wpnumber,Speed,waypoint.roaddist,waypoint.action)) -if Updateroute==nil or Updateroute==true then -self:__UpdateRoute(-0.01) -end -return waypoint -end -function ARMYGROUP:_InitGroup(Template) -if self.groupinitialized then -self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") -return -end -local template=Template or self:_GetTemplate() -self.isAI=true -self.isLateActivated=template.lateActivation -self.isUncontrolled=false -self.speedMax=self.group:GetSpeedMax() -if self.speedMax>3.6 then -self.isMobile=true -else -self.isMobile=false -end -self.speedCruise=self.speedMax*0.7 -self.ammo=self:GetAmmoTot() -self.radio.On=false -self.radio.Freq=133 -self.radio.Modu=radio.modulation.AM -self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,self.radio.On) -self.option.Formation=template.route.points[1].action -self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad -self:SetDefaultTACAN(nil,nil,nil,nil,true) -self.tacan=UTILS.DeepCopy(self.tacanDefault) -local units=self.group:GetUnits() -local dcsgroup=Group.getByName(self.groupname) -local size0=dcsgroup:getInitialSize() -if#units~=size0 then -self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!",#units,size0)) -end -for _,unit in pairs(units)do -local unitname=unit:GetName() -self:_AddElementByName(unitname) -end -self.groupinitialized=true -return self -end -function ARMYGROUP:SwitchFormation(Formation,Permanently,NoRouteUpdate) -if self:IsAlive()or self:IsInUtero()then -Formation=Formation or(self.optionDefault.Formation or"Off road") -Permanently=Permanently or false -if Permanently then -self.formationPerma=Formation -else -self.formationPerma=nil -end -self.option.Formation=Formation or"Off road" -if self:IsInUtero()then -self:T(self.lid..string.format("Will switch formation to %s (permanently=%s) when group is spawned",tostring(self.option.Formation),tostring(Permanently))) -else -if NoRouteUpdate then -else -self:__UpdateRoute(-1,nil,nil,Formation) -end -self:T(self.lid..string.format("Switching formation to %s (permanently=%s)",tostring(self.option.Formation),tostring(Permanently))) -end -end -return self -end -function ARMYGROUP:FindNearestAmmoSupply(Radius) -Radius=UTILS.NMToMeters(Radius or 30) -local coord=self:GetCoordinate() -local myCoalition=self:GetCoalition() -local units=coord:ScanUnits(Radius) -local dmin=math.huge -local truck=nil -for _,_unit in pairs(units.Set)do -local unit=_unit -if unit:IsAlive()and unit:GetCoalition()==myCoalition and unit:IsAmmoSupply()and unit:GetVelocityKMH()<1 then -local d=coord:Get2DDistance(unit:GetCoord()) -if dself.TsuppressionOver then -self.TsuppressionOver=Tnow+Tsuppress -else -renew=false -end -end -if renew then -self:__Unsuppressed(self.TsuppressionOver-Tnow) -end -self:T(self.lid..string.format("Suppressed for %d sec",Tsuppress)) -end -function ARMYGROUP:onbeforeUnsuppressed(From,Event,To) -local Tnow=timer.getTime() -self:T(self.lid..string.format("onbeforeRecovered: Time now: %d - Time over: %d",Tnow,self.TsuppressionOver)) -if Tnow>=self.TsuppressionOver then -return true -else -return false -end -end -function ARMYGROUP:onafterUnsuppressed(From,Event,To) -local text=string.format("Group %s has recovered!",self:GetName()) -MESSAGE:New(text,10):ToAll() -self:T(self.lid..text) -self:SwitchROE(self.suppressionROE) -if true then -self.group:FlareGreen() -end -end -AUFTRAG={ -ClassName="AUFTRAG", -verbose=0, -lid=nil, -auftragsnummer=nil, -groupdata={}, -legions={}, -statusLegion={}, -requestID={}, -assets={}, -NassetsLegMin={}, -NassetsLegMax={}, -missionFraction=0.5, -enrouteTasks={}, -marker=nil, -markerOn=nil, -markerCoalition=nil, -conditionStart={}, -conditionSuccess={}, -conditionFailure={}, -conditionPush={}, -conditionSuccessSet=false, -conditionFailureSet=false, -} -_AUFTRAGSNR=0 -AUFTRAG.Type={ -ANTISHIP="Anti Ship", -AWACS="AWACS", -BAI="BAI", -BOMBING="Bombing", -BOMBRUNWAY="Bomb Runway", -BOMBCARPET="Carpet Bombing", -CAP="CAP", -CAS="CAS", -ESCORT="Escort", -FAC="FAC", -FACA="FAC-A", -FERRY="Ferry Flight", -GROUNDESCORT="Ground Escort", -INTERCEPT="Intercept", -ORBIT="Orbit", -GCICAP="Ground Controlled CAP", -RECON="Recon", -RECOVERYTANKER="Recovery Tanker", -RESCUEHELO="Rescue Helo", -SEAD="SEAD", -STRIKE="Strike", -TANKER="Tanker", -TROOPTRANSPORT="Troop Transport", -ARTY="Fire At Point", -PATROLZONE="Patrol Zone", -OPSTRANSPORT="Ops Transport", -AMMOSUPPLY="Ammo Supply", -FUELSUPPLY="Fuel Supply", -ALERT5="Alert5", -ONGUARD="On Guard", -ARMOREDGUARD="Armored Guard", -BARRAGE="Barrage", -ARMORATTACK="Armor Attack", -CASENHANCED="CAS Enhanced", -HOVER="Hover", -LANDATCOORDINATE="Land at Coordinate", -GROUNDATTACK="Ground Attack", -CARGOTRANSPORT="Cargo Transport", -RELOCATECOHORT="Relocate Cohort", -AIRDEFENSE="Air Defence", -EWR="Early Warning Radar", -REARMING="Rearming", -CAPTUREZONE="Capture Zone", -NOTHING="Nothing", -PATROLRACETRACK="Patrol Racetrack", -} -AUFTRAG.SpecialTask={ -FORMATION="Formation", -PATROLZONE="PatrolZone", -RECON="ReconMission", -AMMOSUPPLY="Ammo Supply", -FUELSUPPLY="Fuel Supply", -ALERT5="Alert5", -ONGUARD="On Guard", -ARMOREDGUARD="ArmoredGuard", -BARRAGE="Barrage", -ARMORATTACK="AmorAttack", -HOVER="Hover", -GROUNDATTACK="Ground Attack", -FERRY="Ferry", -RELOCATECOHORT="Relocate Cohort", -AIRDEFENSE="Air Defense", -EWR="Early Warning Radar", -RECOVERYTANKER="Recovery Tanker", -REARMING="Rearming", -CAPTUREZONE="Capture Zone", -NOTHING="Nothing", -PATROLRACETRACK="Patrol Racetrack", -} -AUFTRAG.Status={ -PLANNED="planned", -QUEUED="queued", -REQUESTED="requested", -SCHEDULED="scheduled", -STARTED="started", -EXECUTING="executing", -DONE="done", -CANCELLED="cancelled", -SUCCESS="success", -FAILED="failed", -} -AUFTRAG.GroupStatus={ -SCHEDULED="scheduled", -STARTED="started", -EXECUTING="executing", -PAUSED="paused", -DONE="done", -CANCELLED="cancelled", -} -AUFTRAG.TargetType={ -GROUP="Group", -UNIT="Unit", -STATIC="Static", -COORDINATE="Coordinate", -AIRBASE="Airbase", -SETGROUP="SetGroup", -SETUNIT="SetUnit", -} -AUFTRAG.Category={ -ALL="All", -AIRCRAFT="Aircraft", -AIRPLANE="Airplane", -HELICOPTER="Helicopter", -GROUND="Ground", -NAVAL="Naval", -} -AUFTRAG.version="1.2.1" -function AUFTRAG:New(Type) -local self=BASE:Inherit(self,FSM:New()) -_AUFTRAGSNR=_AUFTRAGSNR+1 -self.type=Type -self.auftragsnummer=_AUFTRAGSNR -self:_SetLogID() -self.status=AUFTRAG.Status.PLANNED -self:SetName() -self:SetPriority() -self:SetTime() -self:SetRequiredAssets() -self.engageAsGroup=true -self.dTevaluate=5 -self.repeated=0 -self.repeatedSuccess=0 -self.repeatedFailure=0 -self.Nrepeat=0 -self.NrepeatFailure=0 -self.NrepeatSuccess=0 -self.Ncasualties=0 -self.Nkills=0 -self.Nelements=0 -self.Ngroups=0 -self.Nassigned=nil -self.Ndead=0 -self:SetStartState(self.status) -self:AddTransition("*","Planned",AUFTRAG.Status.PLANNED) -self:AddTransition(AUFTRAG.Status.PLANNED,"Queued",AUFTRAG.Status.QUEUED) -self:AddTransition(AUFTRAG.Status.QUEUED,"Requested",AUFTRAG.Status.REQUESTED) -self:AddTransition(AUFTRAG.Status.REQUESTED,"Scheduled",AUFTRAG.Status.SCHEDULED) -self:AddTransition(AUFTRAG.Status.PLANNED,"Scheduled",AUFTRAG.Status.SCHEDULED) -self:AddTransition(AUFTRAG.Status.SCHEDULED,"Started",AUFTRAG.Status.STARTED) -self:AddTransition(AUFTRAG.Status.STARTED,"Executing",AUFTRAG.Status.EXECUTING) -self:AddTransition("*","Done",AUFTRAG.Status.DONE) -self:AddTransition("*","Cancel",AUFTRAG.Status.CANCELLED) -self:AddTransition("*","Success",AUFTRAG.Status.SUCCESS) -self:AddTransition("*","Failed",AUFTRAG.Status.FAILED) -self:AddTransition("*","Status","*") -self:AddTransition("*","Stop","*") -self:AddTransition("*","Repeat",AUFTRAG.Status.PLANNED) -self:AddTransition("*","ElementDestroyed","*") -self:AddTransition("*","GroupDead","*") -self:AddTransition("*","AssetDead","*") -self:__Status(-1) -return self -end -function AUFTRAG:NewANTISHIP(Target,Altitude) -local mission=AUFTRAG:New(AUFTRAG.Type.ANTISHIP) -mission:_TargetFromObject(Target) -mission.engageWeaponType=ENUMS.WeaponFlag.Auto -mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL -mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) -mission.missionTask=ENUMS.MissionTask.ANTISHIPSTRIKE -mission.missionAltitude=mission.engageAltitude -mission.missionFraction=0.4 -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.EvadeFire -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewHOVER(Coordinate,Altitude,Time,Speed,MissionAlt) -local mission=AUFTRAG:New(AUFTRAG.Type.HOVER) -if Altitude then -mission.hoverAltitude=Coordinate:GetLandHeight()+UTILS.FeetToMeters(Altitude) -else -mission.hoverAltitude=Coordinate:GetLandHeight()+UTILS.FeetToMeters(50) -end -mission:_TargetFromObject(Coordinate) -mission.hoverSpeed=0.1 -mission.hoverTime=Time or 300 -self:SetMissionSpeed(Speed or 150) -self:SetMissionAltitude(MissionAlt or 1000) -mission.missionFraction=0.9 -mission.optionROE=ENUMS.ROE.ReturnFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.HELICOPTER} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewLANDATCOORDINATE(Coordinate,OuterRadius,InnerRadius,Time,Speed,MissionAlt) -local mission=AUFTRAG:New(AUFTRAG.Type.LANDATCOORDINATE) -mission:_TargetFromObject(Coordinate) -mission.stayTime=Time or 300 -mission.stayAt=Coordinate -self:SetMissionSpeed(Speed or 150) -self:SetMissionAltitude(MissionAlt or 1000) -if OuterRadius then -mission.stayAt=Coordinate:GetRandomCoordinateInRadius(UTILS.FeetToMeters(OuterRadius),UTILS.FeetToMeters(InnerRadius or 0)) -end -mission.missionFraction=0.9 -mission.optionROE=ENUMS.ROE.ReturnFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.HELICOPTER} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewPATROL_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg,Formation) -local mission=AUFTRAG:New(AUFTRAG.Type.PATROLRACETRACK) -mission:_TargetFromObject(Coordinate) -if Altitude then -mission.TrackAltitude=UTILS.FeetToMeters(Altitude) -else -mission.TrackAltitude=UTILS.FeetToMeters(20000) -end -mission.TrackPoint1=Coordinate -local leg=UTILS.NMToMeters(Leg)or UTILS.NMToMeters(10) -local heading=Heading or 90 -if heading<0 or heading>360 then heading=90 end -mission.TrackPoint2=Coordinate:Translate(leg,heading,true) -mission.TrackSpeed=UTILS.IasToTas(UTILS.KnotsToKmph(Speed or 300),mission.TrackAltitude) -mission.missionSpeed=UTILS.KnotsToKmph(Speed or 300) -mission.missionAltitude=mission.TrackAltitude*0.9 -mission.missionTask=ENUMS.MissionTask.CAP -mission.optionROE=ENUMS.ROE.ReturnFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewORBIT(Coordinate,Altitude,Speed,Heading,Leg) -local mission=AUFTRAG:New(AUFTRAG.Type.ORBIT) -mission:_TargetFromObject(Coordinate) -if Altitude then -mission.orbitAltitude=UTILS.FeetToMeters(Altitude) -else -mission.orbitAltitude=Coordinate.y -end -mission.orbitSpeed=UTILS.IasToTas(UTILS.KnotsToMps(Speed or 350),mission.orbitAltitude) -mission.missionSpeed=UTILS.KnotsToKmph(Speed or 350) -if Leg then -mission.orbitLeg=UTILS.NMToMeters(Leg) -if Heading and Heading<0 then -mission.orbitHeadingRel=true -Heading=-Heading -end -mission.orbitHeading=Heading -end -mission.missionAltitude=mission.orbitAltitude*0.9 -mission.missionFraction=0.9 -mission.optionROE=ENUMS.ROE.ReturnFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewORBIT_CIRCLE(Coordinate,Altitude,Speed) -local mission=AUFTRAG:NewORBIT(Coordinate,Altitude,Speed) -return mission -end -function AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg) -Heading=Heading or math.random(360) -Leg=Leg or 10 -local mission=AUFTRAG:NewORBIT(Coordinate,Altitude,Speed,Heading,Leg) -return mission -end -function AUFTRAG:NewORBIT_GROUP(Group,Altitude,Speed,Leg,Heading,OffsetVec2,Distance) -Altitude=Altitude or 6000 -local mission=AUFTRAG:NewORBIT(Group,Altitude,Speed,Heading,Leg) -mission.updateDCSTask=true -if OffsetVec2 then -if OffsetVec2.x then -OffsetVec2.x=UTILS.NMToMeters(OffsetVec2.x) -end -if OffsetVec2.y then -OffsetVec2.y=UTILS.NMToMeters(OffsetVec2.y) -end -if OffsetVec2.r then -OffsetVec2.r=UTILS.NMToMeters(OffsetVec2.r) -end -end -mission.orbitOffsetVec2=OffsetVec2 -mission.orbitDeltaR=UTILS.NMToMeters(Distance or 5) -mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewGCICAP(Coordinate,Altitude,Speed,Heading,Leg) -local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg) -mission.type=AUFTRAG.Type.GCICAP -mission:_SetLogID() -mission.missionTask=ENUMS.MissionTask.INTERCEPT -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.AIRCRAFT} -return mission -end -function AUFTRAG:NewTANKER(Coordinate,Altitude,Speed,Heading,Leg,RefuelSystem) -local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg) -mission.type=AUFTRAG.Type.TANKER -mission:_SetLogID() -mission.refuelSystem=RefuelSystem -mission.missionTask=ENUMS.MissionTask.REFUELING -mission.optionROE=ENUMS.ROE.WeaponHold -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) -local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg) -mission.type=AUFTRAG.Type.AWACS -mission:_SetLogID() -mission.missionTask=ENUMS.MissionTask.AWACS -mission.optionROE=ENUMS.ROE.WeaponHold -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewINTERCEPT(Target) -local mission=AUFTRAG:New(AUFTRAG.Type.INTERCEPT) -mission:_TargetFromObject(Target) -mission.missionTask=ENUMS.MissionTask.INTERCEPT -mission.missionFraction=0.1 -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.EvadeFire -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewCAP(ZoneCAP,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes) -TargetTypes=UTILS.EnsureTable(TargetTypes,true) -Altitude=Altitude or 10000 -local mission=AUFTRAG:NewORBIT(Coordinate or ZoneCAP:GetCoordinate(),Altitude,Speed or 350,Heading,Leg) -mission.type=AUFTRAG.Type.CAP -mission:_SetLogID() -mission.engageZone=ZoneCAP -mission.engageTargetTypes=TargetTypes or{"Air"} -mission.missionTask=ENUMS.MissionTask.CAP -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.EvadeFire -mission.missionSpeed=UTILS.KnotsToKmph(UTILS.KnotsToAltKIAS(Speed or 350,Altitude)) -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewCAPGROUP(Grp,Altitude,Speed,RelHeading,Leg,OffsetDist,OffsetAngle,UpdateDistance,TargetTypes,EngageRange) -TargetTypes=UTILS.EnsureTable(TargetTypes,true) -local OffsetVec2={r=OffsetDist or 6,phi=OffsetAngle or 180} -Leg=Leg or 14 -local Heading=nil -if RelHeading then -Heading=-math.abs(RelHeading) -end -local mission=AUFTRAG:NewORBIT_GROUP(Grp,Altitude,Speed,Leg,Heading,OffsetVec2,UpdateDistance) -mission.type=AUFTRAG.Type.CAP -mission:_SetLogID() -local engage=EngageRange or 32 -local zoneCAPGroup=ZONE_GROUP:New("CAPGroup",Grp,UTILS.NMToMeters(engage)) -mission.engageZone=zoneCAPGroup -mission.engageTargetTypes=TargetTypes or{"Air"} -mission.missionTask=ENUMS.MissionTask.CAP -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.EvadeFire -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewCAS(ZoneCAS,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes) -TargetTypes=UTILS.EnsureTable(TargetTypes,true) -local mission=AUFTRAG:NewORBIT(Coordinate or ZoneCAS:GetCoordinate(),Altitude or 10000,Speed,Heading,Leg) -mission.type=AUFTRAG.Type.CAS -mission:_SetLogID() -mission.engageZone=ZoneCAS -mission.engageTargetTypes=TargetTypes or{"Helicopters","Ground Units","Light armed ships"} -mission.missionTask=ENUMS.MissionTask.CAS -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.EvadeFire -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewCASENHANCED(CasZone,Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes) -local mission=AUFTRAG:New(AUFTRAG.Type.CASENHANCED) -if type(CasZone)=="string"then -CasZone=ZONE:New(CasZone) -end -mission:_TargetFromObject(CasZone) -mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CASENHANCED) -mission:SetEngageDetected(RangeMax,TargetTypes or{"Helicopters","Ground Units","Light armed ships"},CasZone,NoEngageZoneSet) -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.EvadeFire -mission.missionFraction=0.5 -mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil -mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil -mission.dTevaluate=15 -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewFAC(FacZone,Speed,Altitude,Frequency,Modulation) -local mission=AUFTRAG:New(AUFTRAG.Type.FAC) -if type(FacZone)=="string"then -FacZone=ZONE:FindByName(FacZone) -end -mission:_TargetFromObject(FacZone) -mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.FAC) -mission.facFreq=Frequency or 133 -mission.facModu=Modulation or radio.modulation.AM -mission.optionROE=ENUMS.ROE.ReturnFire -mission.optionROT=ENUMS.ROT.EvadeFire -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.missionFraction=1.0 -mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil -mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil -mission.categories={AUFTRAG.Category.AIRCRAFT,AUFTRAG.Category.GROUND} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewFACA(Target,Designation,DataLink,Frequency,Modulation) -local mission=AUFTRAG:New(AUFTRAG.Type.FACA) -mission:_TargetFromObject(Target) -mission.facDesignation=Designation -mission.facDatalink=true -mission.facFreq=Frequency or 133 -mission.facModu=Modulation or radio.modulation.AM -mission.missionTask=ENUMS.MissionTask.AFAC -mission.missionAltitude=nil -mission.missionFraction=0.5 -mission.optionROE=ENUMS.ROE.ReturnFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewBAI(Target,Altitude) -local mission=AUFTRAG:New(AUFTRAG.Type.BAI) -mission:_TargetFromObject(Target) -mission.engageWeaponType=ENUMS.WeaponFlag.Auto -mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL -mission.engageAltitude=UTILS.FeetToMeters(Altitude or 5000) -mission.missionTask=ENUMS.MissionTask.GROUNDATTACK -mission.missionAltitude=mission.engageAltitude -mission.missionFraction=0.75 -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewSEAD(Target,Altitude) -local mission=AUFTRAG:New(AUFTRAG.Type.SEAD) -mission:_TargetFromObject(Target) -mission.engageWeaponType=ENUMS.WeaponFlag.Auto -mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL -mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) -mission.missionTask=ENUMS.MissionTask.SEAD -mission.missionAltitude=mission.engageAltitude -mission.missionFraction=0.2 -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.EvadeFire -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewSTRIKE(Target,Altitude) -local mission=AUFTRAG:New(AUFTRAG.Type.STRIKE) -mission:_TargetFromObject(Target) -mission.engageWeaponType=ENUMS.WeaponFlag.Auto -mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL -mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) -mission.missionTask=ENUMS.MissionTask.GROUNDATTACK -mission.missionAltitude=mission.engageAltitude -mission.missionFraction=0.75 -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewBOMBING(Target,Altitude) -local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING) -mission:_TargetFromObject(Target) -mission.engageWeaponType=ENUMS.WeaponFlag.Auto -mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL -mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) -mission.missionTask=ENUMS.MissionTask.GROUNDATTACK -mission.missionAltitude=mission.engageAltitude*0.8 -mission.missionFraction=0.5 -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.NoReaction -mission.dTevaluate=5*60 -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewBOMBRUNWAY(Airdrome,Altitude) -if type(Airdrome)=="string"then -Airdrome=AIRBASE:FindByName(Airdrome) -end -local mission=AUFTRAG:New(AUFTRAG.Type.BOMBRUNWAY) -mission:_TargetFromObject(Airdrome) -mission.engageWeaponType=ENUMS.WeaponFlag.Auto -mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL -mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) -mission.missionTask=ENUMS.MissionTask.RUNWAYATTACK -mission.missionAltitude=mission.engageAltitude*0.8 -mission.missionFraction=0.75 -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.dTevaluate=5*60 -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewBOMBCARPET(Target,Altitude,CarpetLength) -local mission=AUFTRAG:New(AUFTRAG.Type.BOMBCARPET) -mission:_TargetFromObject(Target) -mission.engageWeaponType=ENUMS.WeaponFlag.Auto -mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL -mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) -mission.engageCarpetLength=CarpetLength or 500 -mission.engageAsGroup=false -mission.engageDirection=nil -mission.missionTask=ENUMS.MissionTask.GROUNDATTACK -mission.missionAltitude=mission.engageAltitude*0.8 -mission.missionFraction=0.5 -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.NoReaction -mission.dTevaluate=5*60 -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewGROUNDESCORT(EscortGroup,OrbitDistance,TargetTypes) -local mission=AUFTRAG:New(AUFTRAG.Type.GROUNDESCORT) -if type(EscortGroup)=="string"then -mission.escortGroupName=EscortGroup -mission:_TargetFromObject() -else -mission:_TargetFromObject(EscortGroup) -end -mission.orbitDistance=OrbitDistance and UTILS.NMToMeters(OrbitDistance)or UTILS.NMToMeters(1.5) -mission.engageTargetTypes=TargetTypes or{"Ground vehicles"} -mission.missionTask=ENUMS.MissionTask.GROUNDESCORT -mission.missionFraction=0.1 -mission.missionAltitude=100 -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.EvadeFire -mission.categories={AUFTRAG.Category.HELICOPTER} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewESCORT(EscortGroup,OffsetVector,EngageMaxDistance,TargetTypes) -local mission=AUFTRAG:New(AUFTRAG.Type.ESCORT) -if type(EscortGroup)=="string"then -mission.escortGroupName=EscortGroup -mission:_TargetFromObject() -else -mission:_TargetFromObject(EscortGroup) -end -mission.escortVec3=OffsetVector or{x=-100,y=0,z=200} -mission.engageMaxDistance=EngageMaxDistance and UTILS.NMToMeters(EngageMaxDistance)or UTILS.NMToMeters(32) -mission.engageTargetTypes=TargetTypes or{"Air"} -mission.missionTask=ENUMS.MissionTask.ESCORT -mission.missionFraction=0.1 -mission.missionAltitude=1000 -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewRESCUEHELO(Carrier) -local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) -mission:_TargetFromObject(Carrier) -mission.missionTask=ENUMS.MissionTask.NOTHING -mission.missionFraction=0.5 -mission.optionROE=ENUMS.ROE.WeaponHold -mission.optionROT=ENUMS.ROT.NoReaction -mission.categories={AUFTRAG.Category.HELICOPTER} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewRECOVERYTANKER(Carrier,Altitude,Speed,Leg,RelHeading,OffsetDist,OffsetAngle,UpdateDistance) -local OffsetVec2={r=OffsetDist or 6,phi=OffsetAngle or 180} -Leg=Leg or 14 -Speed=Speed or 250 -local Heading=nil -if RelHeading then -Heading=-math.abs(RelHeading) -end -local mission=AUFTRAG:NewORBIT_GROUP(Carrier,Altitude,Speed,Leg,Heading,OffsetVec2,UpdateDistance) -mission.type=AUFTRAG.Type.RECOVERYTANKER -mission.missionTask=ENUMS.MissionTask.REFUELING -mission.missionFraction=0.9 -mission.optionROE=ENUMS.ROE.WeaponHold -mission.optionROT=ENUMS.ROT.NoReaction -mission.categories={AUFTRAG.Category.AIRPLANE} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet,DropoffCoordinate,PickupCoordinate,PickupRadius) -local mission=AUFTRAG:New(AUFTRAG.Type.TROOPTRANSPORT) -if TransportGroupSet:IsInstanceOf("GROUP")then -mission.transportGroupSet=SET_GROUP:New() -mission.transportGroupSet:AddGroup(TransportGroupSet) -elseif TransportGroupSet:IsInstanceOf("SET_GROUP")then -mission.transportGroupSet=TransportGroupSet -else -mission:E(mission.lid.."ERROR: TransportGroupSet must be a GROUP or SET_GROUP object!") -return nil -end -mission:_TargetFromObject(mission.transportGroupSet) -mission.transportPickup=PickupCoordinate or mission:GetTargetCoordinate() -mission.transportDropoff=DropoffCoordinate -mission.transportPickupRadius=PickupRadius or 100 -mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.TROOPTRANSPORT) -mission.optionROE=ENUMS.ROE.ReturnFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.HELICOPTER,AUFTRAG.Category.GROUND} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewCARGOTRANSPORT(StaticCargo,DropZone) -local mission=AUFTRAG:New(AUFTRAG.Type.CARGOTRANSPORT) -mission:_TargetFromObject(StaticCargo) -mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CARGOTRANSPORT) -mission.optionROE=ENUMS.ROE.ReturnFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.categories={AUFTRAG.Category.HELICOPTER} -mission.DCStask=mission:GetDCSMissionTask() -mission.DCStask.params.groupId=StaticCargo:GetID() -mission.DCStask.params.zoneId=DropZone.ZoneID -mission.DCStask.params.zone=DropZone -mission.DCStask.params.cargo=StaticCargo -return mission -end -function AUFTRAG:NewARTY(Target,Nshots,Radius,Altitude) -local mission=AUFTRAG:New(AUFTRAG.Type.ARTY) -mission:_TargetFromObject(Target) -mission.artyShots=Nshots or nil -mission.artyRadius=Radius or 100 -mission.artyAltitude=Altitude -mission.engageWeaponType=ENUMS.WeaponFlag.Auto -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionAlarm=0 -mission.missionFraction=0.0 -mission.dTevaluate=8*60 -mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewBARRAGE(Zone,Heading,Angle,Radius,Altitude,Nshots) -local mission=AUFTRAG:New(AUFTRAG.Type.BARRAGE) -mission:_TargetFromObject(Zone) -mission.artyShots=Nshots -mission.artyRadius=Radius or 100 -mission.artyAltitude=Altitude -mission.artyHeading=Heading -mission.artyAngle=Angle -mission.engageWeaponType=ENUMS.WeaponFlag.Auto -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionAlarm=0 -mission.missionFraction=0.0 -mission.dTevaluate=10 -mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewPATROLZONE(Zone,Speed,Altitude,Formation) -local mission=AUFTRAG:New(AUFTRAG.Type.PATROLZONE) -if type(Zone)=="string"then -Zone=ZONE:New(Zone) -end -mission:_TargetFromObject(Zone) -mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.PATROLZONE) -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.missionFraction=1.0 -mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil -mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil -mission.categories={AUFTRAG.Category.ALL} -mission.DCStask=mission:GetDCSMissionTask() -mission.DCStask.params.formation=Formation or"Off Road" -return mission -end -function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation) -local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE) -mission:_TargetFromObject(OpsZone) -mission.coalition=Coalition -mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CAPTUREZONE) -mission.optionROE=ENUMS.ROE.ReturnFire -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.missionFraction=0.1 -mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil -mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil -mission.categories={AUFTRAG.Category.ALL} -mission.DCStask=mission:GetDCSMissionTask() -mission.updateDCSTask=true -local params={} -params.formation=Formation or"Off Road" -params.zone=mission:GetObjective() -params.altitude=mission.missionAltitude -params.speed=mission.missionSpeed -mission.DCStask.params=params -return mission -end -function AUFTRAG:NewARMORATTACK(Target,Speed,Formation) -local mission=AUFTRAG:NewGROUNDATTACK(Target,Speed,Formation) -mission.type=AUFTRAG.Type.ARMORATTACK -return mission -end -function AUFTRAG:NewGROUNDATTACK(Target,Speed,Formation) -local mission=AUFTRAG:New(AUFTRAG.Type.GROUNDATTACK) -mission:_TargetFromObject(Target) -mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.GROUNDATTACK) -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.optionFormation="On Road" -mission.missionFraction=0.70 -mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil -mission.categories={AUFTRAG.Category.GROUND} -mission.DCStask=mission:GetDCSMissionTask() -mission.DCStask.params.speed=Speed -mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee -return mission -end -function AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation) -local mission=AUFTRAG:New(AUFTRAG.Type.RECON) -mission:_TargetFromObject(ZoneSet) -if ZoneSet:IsInstanceOf("SET_ZONE")then -mission.missionZoneSet=ZoneSet -elseif ZoneSet:IsInstanceOf("ZONE_BASE")then -mission.missionZoneSet=SET_ZONE:New() -mission.missionZoneSet:AddZone(ZoneSet) -end -mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.RECON) -mission.optionROE=ENUMS.ROE.WeaponHold -mission.optionROT=ENUMS.ROT.PassiveDefense -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.missionFraction=0.5 -mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil -mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or UTILS.FeetToMeters(2000) -mission.categories={AUFTRAG.Category.ALL} -mission.DCStask=mission:GetDCSMissionTask() -mission.DCStask.params.adinfinitum=Adinfinitum -mission.DCStask.params.randomly=Randomly -mission.DCStask.params.formation=Formation -return mission -end -function AUFTRAG:NewAMMOSUPPLY(Zone) -local mission=AUFTRAG:New(AUFTRAG.Type.AMMOSUPPLY) -mission:_TargetFromObject(Zone) -mission.optionROE=ENUMS.ROE.WeaponHold -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.missionFraction=1.0 -mission.missionWaypointRadius=0 -mission.categories={AUFTRAG.Category.GROUND} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewFUELSUPPLY(Zone) -local mission=AUFTRAG:New(AUFTRAG.Type.FUELSUPPLY) -mission:_TargetFromObject(Zone) -mission.optionROE=ENUMS.ROE.WeaponHold -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.missionFraction=1.0 -mission.categories={AUFTRAG.Category.GROUND} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewREARMING(Zone) -local mission=AUFTRAG:New(AUFTRAG.Type.REARMING) -mission:_TargetFromObject(Zone) -mission.optionROE=ENUMS.ROE.WeaponHold -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.missionFraction=1.0 -mission.missionWaypointRadius=0 -mission.categories={AUFTRAG.Category.GROUND} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewALERT5(MissionType) -local mission=AUFTRAG:New(AUFTRAG.Type.ALERT5) -mission.missionTask=self:GetMissionTaskforMissionType(MissionType) -mission.optionROE=ENUMS.ROE.WeaponHold -mission.optionROT=ENUMS.ROT.NoReaction -mission.alert5MissionType=MissionType -mission.missionFraction=1.0 -mission.categories={AUFTRAG.Category.AIRCRAFT} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewONGUARD(Coordinate) -local mission=AUFTRAG:New(AUFTRAG.Type.ONGUARD) -mission:_TargetFromObject(Coordinate) -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.missionFraction=1.0 -mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewAIRDEFENSE(Zone) -local mission=AUFTRAG:New(AUFTRAG.Type.AIRDEFENSE) -mission:_TargetFromObject(Zone) -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.missionFraction=1.0 -mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewEWR(Zone) -local mission=AUFTRAG:New(AUFTRAG.Type.EWR) -mission:_TargetFromObject(Zone) -mission.optionROE=ENUMS.ROE.WeaponHold -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.missionFraction=1.0 -mission.categories={AUFTRAG.Category.GROUND} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:_NewRELOCATECOHORT(Legion,Cohort) -local mission=AUFTRAG:New(AUFTRAG.Type.RELOCATECOHORT) -mission:_TargetFromObject(Legion.spawnzone) -mission.optionROE=ENUMS.ROE.ReturnFire -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.missionFraction=0.0 -mission.categories={AUFTRAG.Category.ALL} -mission.DCStask=mission:GetDCSMissionTask() -if Cohort.isGround then -mission.optionFormation=ENUMS.Formation.Vehicle.OnRoad -end -mission.DCStask.params.legion=Legion -mission.DCStask.params.cohort=Cohort -return mission -end -function AUFTRAG:NewNOTHING(RelaxZone) -local mission=AUFTRAG:New(AUFTRAG.Type.NOTHING) -mission:_TargetFromObject(RelaxZone) -mission.optionROE=ENUMS.ROE.WeaponHold -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.missionFraction=1.0 -mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewARMOREDGUARD(Coordinate,Formation) -local mission=AUFTRAG:New(AUFTRAG.Type.ARMOREDGUARD) -mission:_TargetFromObject(Coordinate) -mission.optionROE=ENUMS.ROE.OpenFire -mission.optionAlarm=ENUMS.AlarmState.Auto -mission.optionFormation=Formation or"On Road" -mission.missionFraction=1.0 -mission.categories={AUFTRAG.Category.GROUND} -mission.DCStask=mission:GetDCSMissionTask() -return mission -end -function AUFTRAG:NewFromTarget(Target,MissionType) -local mission=nil -if MissionType==AUFTRAG.Type.ANTISHIP then -mission=self:NewANTISHIP(Target,Altitude) -elseif MissionType==AUFTRAG.Type.ARTY then -mission=self:NewARTY(Target,Nshots,Radius) -elseif MissionType==AUFTRAG.Type.BAI then -mission=self:NewBAI(Target,Altitude) -elseif MissionType==AUFTRAG.Type.BOMBCARPET then -mission=self:NewBOMBCARPET(Target,Altitude,CarpetLength) -elseif MissionType==AUFTRAG.Type.BOMBING then -mission=self:NewBOMBING(Target,Altitude) -elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then -mission=self:NewBOMBRUNWAY(Target,Altitude) -elseif MissionType==AUFTRAG.Type.CAS then -mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,Target:GetAverageCoordinate(),Heading,Leg,TargetTypes) -elseif MissionType==AUFTRAG.Type.CASENHANCED then -mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes) -elseif MissionType==AUFTRAG.Type.INTERCEPT then -mission=self:NewINTERCEPT(Target) -elseif MissionType==AUFTRAG.Type.SEAD then -mission=self:NewSEAD(Target,Altitude) -elseif MissionType==AUFTRAG.Type.STRIKE then -mission=self:NewSTRIKE(Target,Altitude) -elseif MissionType==AUFTRAG.Type.ARMORATTACK then -mission=self:NewARMORATTACK(Target,Speed) -elseif MissionType==AUFTRAG.Type.GROUNDATTACK then -mission=self:NewGROUNDATTACK(Target,Speed,Formation) -else -return nil -end -return mission -end -function AUFTRAG:_DetermineAuftragType(Target) -local group=nil -local airbase=nil -local scenery=nil -local coordinate=nil -local auftrag=nil -if Target:IsInstanceOf("GROUP")then -group=Target -elseif Target:IsInstanceOf("UNIT")then -group=Target:GetGroup() -elseif Target:IsInstanceOf("AIRBASE")then -airbase=Target -elseif Target:IsInstanceOf("SCENERY")then -scenery=Target -end -if group then -local category=group:GetCategory() -local attribute=group:GetAttribute() -if category==Group.Category.AIRPLANE or category==Group.Category.HELICOPTER then -auftrag=AUFTRAG.Type.INTERCEPT -elseif category==Group.Category.GROUND or category==Group.Category.TRAIN then -if attribute==GROUP.Attribute.GROUND_SAM then -auftrag=AUFTRAG.Type.SEAD -elseif attribute==GROUP.Attribute.GROUND_AAA then -auftrag=AUFTRAG.Type.BAI -elseif attribute==GROUP.Attribute.GROUND_ARTILLERY then -auftrag=AUFTRAG.Type.BAI -elseif attribute==GROUP.Attribute.GROUND_INFANTRY then -auftrag=AUFTRAG.Type.CAS -elseif attribute==GROUP.Attribute.GROUND_TANK then -auftrag=AUFTRAG.Type.BAI -else -auftrag=AUFTRAG.Type.BAI -end -elseif category==Group.Category.SHIP then -auftrag=AUFTRAG.Type.ANTISHIP -else -self:T(self.lid.."ERROR: Unknown Group category!") -end -elseif airbase then -auftrag=AUFTRAG.Type.BOMBRUNWAY -elseif scenery then -auftrag=AUFTRAG.Type.STRIKE -elseif coordinate then -auftrag=AUFTRAG.Type.BOMBING -end -return auftrag -end -function AUFTRAG:NewAUTO(EngageGroup) -local mission=nil -local Target=EngageGroup -local auftrag=self:_DetermineAuftragType(EngageGroup) -if auftrag==AUFTRAG.Type.ANTISHIP then -mission=AUFTRAG:NewANTISHIP(Target) -elseif auftrag==AUFTRAG.Type.ARTY then -mission=AUFTRAG:NewARTY(Target) -elseif auftrag==AUFTRAG.Type.AWACS then -mission=AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) -elseif auftrag==AUFTRAG.Type.BAI then -mission=AUFTRAG:NewBAI(Target,Altitude) -elseif auftrag==AUFTRAG.Type.BOMBING then -mission=AUFTRAG:NewBOMBING(Target,Altitude) -elseif auftrag==AUFTRAG.Type.BOMBRUNWAY then -mission=AUFTRAG:NewBOMBRUNWAY(Airdrome,Altitude) -elseif auftrag==AUFTRAG.Type.BOMBCARPET then -mission=AUFTRAG:NewBOMBCARPET(Target,Altitude,CarpetLength) -elseif auftrag==AUFTRAG.Type.CAP then -mission=AUFTRAG:NewCAP(ZoneCAP,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes) -elseif auftrag==AUFTRAG.Type.CAS then -mission=AUFTRAG:NewCAS(ZoneCAS,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes) -elseif auftrag==AUFTRAG.Type.ESCORT then -mission=AUFTRAG:NewESCORT(EscortGroup,OffsetVector,EngageMaxDistance,TargetTypes) -elseif auftrag==AUFTRAG.Type.FACA then -mission=AUFTRAG:NewFACA(Target,Designation,DataLink,Frequency,Modulation) -elseif auftrag==AUFTRAG.Type.FERRY then -elseif auftrag==AUFTRAG.Type.GCICAP then -mission=AUFTRAG:NewGCICAP(Coordinate,Altitude,Speed,Heading,Leg) -elseif auftrag==AUFTRAG.Type.INTERCEPT then -mission=AUFTRAG:NewINTERCEPT(Target) -elseif auftrag==AUFTRAG.Type.ORBIT then -mission=AUFTRAG:NewORBIT(Coordinate,Altitude,Speed,Heading,Leg) -elseif auftrag==AUFTRAG.Type.RECON then -mission=AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation) -elseif auftrag==AUFTRAG.Type.RESCUEHELO then -mission=AUFTRAG:NewRESCUEHELO(Carrier) -elseif auftrag==AUFTRAG.Type.SEAD then -mission=AUFTRAG:NewSEAD(Target,Altitude) -elseif auftrag==AUFTRAG.Type.STRIKE then -mission=AUFTRAG:NewSTRIKE(Target,Altitude) -elseif auftrag==AUFTRAG.Type.TANKER then -mission=AUFTRAG:NewTANKER(Coordinate,Altitude,Speed,Heading,Leg,RefuelSystem) -elseif auftrag==AUFTRAG.Type.TROOPTRANSPORT then -mission=AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet,DropoffCoordinate,PickupCoordinate) -elseif auftrag==AUFTRAG.Type.PATROLRACETRACK then -mission=AUFTRAG:NewPATROL_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg,Formation) -else -end -if mission then -mission:SetPriority(10,true) -end -return mission -end -function AUFTRAG:SetTime(ClockStart,ClockStop) -local Tnow=timer.getAbsTime() -local Tstart=Tnow+5 -if ClockStart and type(ClockStart)=="number"then -Tstart=Tnow+ClockStart -elseif ClockStart and type(ClockStart)=="string"then -Tstart=UTILS.ClockToSeconds(ClockStart) -end -local Tstop=nil -if ClockStop and type(ClockStop)=="number"then -Tstop=Tnow+ClockStop -elseif ClockStop and type(ClockStop)=="string"then -Tstop=UTILS.ClockToSeconds(ClockStop) -end -self.Tstart=Tstart -self.Tstop=Tstop -if Tstop then -self.duration=self.Tstop-self.Tstart -end -return self -end -function AUFTRAG:SetDuration(Duration) -self.durationExe=Duration -return self -end -function AUFTRAG:SetTeleport(Switch) -if Switch==nil then -Switch=true -end -self.teleport=Switch -return self -end -function AUFTRAG:SetReturnToLegion(Switch) -self.legionReturn=Switch -self:T(self.lid..string.format("Setting ReturnToLetion=%s",tostring(self.legionReturn))) -return self -end -function AUFTRAG:SetPushTime(ClockPush) -if ClockPush then -if type(ClockPush)=="string"then -self.Tpush=UTILS.ClockToSeconds(ClockPush) -elseif type(ClockPush)=="number"then -self.Tpush=timer.getAbsTime()+ClockPush -end -end -return self -end -function AUFTRAG:SetPriority(Prio,Urgent,Importance) -self.prio=Prio or 50 -self.urgent=Urgent -self.importance=Importance -return self -end -function AUFTRAG:SetRepeat(Nrepeat) -self.Nrepeat=Nrepeat or 0 -return self -end -function AUFTRAG:SetRepeatOnFailure(Nrepeat) -self.NrepeatFailure=Nrepeat or 0 -return self -end -function AUFTRAG:SetRepeatOnSuccess(Nrepeat) -self.NrepeatSuccess=Nrepeat or 0 -return self -end -function AUFTRAG:SetReinforce(Nreinforce) -self.reinforce=Nreinforce -return self -end -function AUFTRAG:SetRequiredAssets(NassetsMin,NassetsMax) -self.NassetsMin=NassetsMin or 1 -self.NassetsMax=NassetsMax or self.NassetsMin -if self.NassetsMax0 then -local N=self:CountOpsGroups() -if N Nmin=%d",self.NassetsMin,N,self.reinforce,Nmin)) -end -end -end -return Nmin,Nmax -end -function AUFTRAG:SetAssetsStayAlive(Switch) -if Switch==nil then -Switch=true -end -self.assetStayAlive=Switch -return self -end -function AUFTRAG:SetRequiredEscorts(NescortMin,NescortMax,MissionType,TargetTypes,EngageRange) -self.NescortMin=NescortMin or 1 -self.NescortMax=NescortMax or self.NescortMin -if self.NescortMaxself.Tstop then -return false -end -local startme=self:EvalConditionsAll(self.conditionStart) -if not startme then -return false -end -return true -end -function AUFTRAG:IsReadyToCancel() -local Tnow=timer.getAbsTime() -if self.Tstop and Tnow>=self.Tstop then -return true -end -local failure=self:EvalConditionsAny(self.conditionFailure) -if failure then -self.failurecondition=true -return true -end -local success=self:EvalConditionsAny(self.conditionSuccess) -if success then -self.successcondition=true -return true -end -return false -end -function AUFTRAG:IsReadyToPush() -local Tnow=timer.getAbsTime() -if self.Tpush and Tnow<=self.Tpush then -return false -end -local push=self:EvalConditionsAll(self.conditionPush) -return push -end -function AUFTRAG:EvalConditionsAll(Conditions) -for _,_condition in pairs(Conditions or{})do -local condition=_condition -local istrue=condition.func(unpack(condition.arg)) -if not istrue then -return false -end -end -return true -end -function AUFTRAG:EvalConditionsAny(Conditions) -for _,_condition in pairs(Conditions or{})do -local condition=_condition -local istrue=condition.func(unpack(condition.arg)) -if istrue then -return true -end -end -return false -end -function AUFTRAG:onafterStatus(From,Event,To) -local Tnow=timer.getAbsTime() -if self.escortGroupName then -local group=GROUP:FindByName(self.escortGroupName) -if group and group:IsAlive()then -self:T(self.lid..string.format("ESCORT group %s is now alive. Updating DCS task and adding group to TARGET",tostring(self.escortGroupName))) -self.engageTarget:AddObject(group) -self.DCStask=self:GetDCSMissionTask() -self.escortGroupName=nil -end -end -local Ntargets=self:CountMissionTargets() -local Ntargets0=self:GetTargetInitialNumber() -local Ngroups=self:CountOpsGroups() -local Nassigned=self.Nassigned and self.Nassigned-self.Ndead or 0 -local conditionDone=false -if self.conditionFailureSet then -conditionDone=self:EvalConditionsAny(self.conditionFailure) -end -if self.conditionSuccessSet and not conditionDone then -conditionDone=self:EvalConditionsAny(self.conditionSuccess) -end -if self:IsNotOver()then -if self:CheckGroupsDone()then -self:Done() -elseif(self.Tstop and Tnow>self.Tstop+10)then -self:Cancel() -elseif conditionDone then -self:Cancel() -elseif self.durationExe and self.Texecuting and Tnow-self.Texecuting>self.durationExe then -local Nrepeat=self.Nrepeat -local NrepeatS=self.NrepeatSuccess -local NrepeatF=self.NrepeatFailure -self:Cancel() -self.Nrepeat=Nrepeat -self.NrepeatSuccess=NrepeatS -self.NrepeatFailure=NrepeatF -elseif(Ntargets0>0 and Ntargets==0)then -self:T(self.lid.."No targets left cancelling mission!") -self:Cancel() -elseif self:IsExecuting()and self:_IsNotReinforcing()then -if Ngroups==0 then -self:Done() -else -local done=true -for groupname,data in pairs(self.groupdata or{})do -local groupdata=data -local opsgroup=groupdata.opsgroup -if opsgroup:IsAlive()then -done=false -end -end -if done then -self:Done() -end -end -end -end -local fsmstate=self:GetState() -if fsmstate~=self.status then -self:T(self.lid..string.format("ERROR: FSM state %s != %s mission status!",fsmstate,self.status)) -end -if self.verbose>=1 then -local Cstart=UTILS.SecondsToClock(self.Tstart,true) -local Cstop=self.Tstop and UTILS.SecondsToClock(self.Tstop,true)or"INF" -local targetname=self:GetTargetName()or"unknown" -local Nlegions=#self.legions -local commander=self.commander and self.statusCommander or"N/A" -local chief=self.chief and self.statusChief or"N/A" -self:T(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, legions=%d, commander=%s, chief=%s", -self.status,targetname,Cstart,Cstop,#self.assets,Ngroups,Ntargets,Nlegions,commander,chief)) -end -if self.verbose>=2 then -local text="Group data:" -for groupname,_groupdata in pairs(self.groupdata)do -local groupdata=_groupdata -text=text..string.format("\n- %s: status mission=%s opsgroup=%s",groupname,groupdata.status,groupdata.opsgroup and groupdata.opsgroup:GetState()or"N/A") -end -self:I(self.lid..text) -end -if self.verbose>=3 then -local text=string.format("Assets [N=%d,Nassigned=%s, Ndead=%s]:",self.Nassets or 0,self.Nassigned or 0,self.Ndead or 0) -for i,_asset in pairs(self.assets or{})do -local asset=_asset -text=text..string.format("\n[%d] %s: spawned=%s, requested=%s, reserved=%s",i,asset.spawngroupname,tostring(asset.spawned),tostring(asset.requested),tostring(asset.reserved)) -end -self:I(self.lid..text) -end -local ready2evaluate=self.Tover and Tnow-self.Tover>=self.dTevaluate or false -if self:IsOver()and ready2evaluate then -self:Evaluate() -else -self:__Status(-30) -end -if self.markerOn then -self:UpdateMarker() -end -end -function AUFTRAG:Evaluate() -local failed=false -local targetdamage=self:GetTargetDamage() -local owndamage=self.Ncasualties/self.Nelements*100 -local Ntargets=self:CountMissionTargets() -local Ntargets0=self:GetTargetInitialNumber() -local Life=self:GetTargetLife() -local Life0=self:GetTargetInitialLife() -if Ntargets0>0 then -if self.type==AUFTRAG.Type.TROOPTRANSPORT or self.type==AUFTRAG.Type.ESCORT then -if Ntargets0 then -failed=true -end -end -else -if self.Nelements==self.Ncasualties then -failed=true -end -end -local successCondition=self:EvalConditionsAny(self.conditionSuccess) -local failureCondition=self:EvalConditionsAny(self.conditionFailure) -if failureCondition then -failed=true -elseif successCondition then -failed=false -end -if self.verbose>0 then -local text=string.format("Evaluating mission:\n") -text=text..string.format("Own casualties = %d/%d\n",self.Ncasualties,self.Nelements) -text=text..string.format("Own losses = %.1f %%\n",owndamage) -text=text..string.format("Killed units = %d\n",self.Nkills) -text=text..string.format("--------------------------\n") -text=text..string.format("Targets left = %d/%d\n",Ntargets,Ntargets0) -text=text..string.format("Targets life = %.1f/%.1f\n",Life,Life0) -text=text..string.format("Enemy losses = %.1f %%\n",targetdamage) -text=text..string.format("--------------------------\n") -text=text..string.format("Success Cond = %s\n",tostring(successCondition)) -text=text..string.format("Failure Cond = %s\n",tostring(failureCondition)) -text=text..string.format("--------------------------\n") -text=text..string.format("Final Success = %s\n",tostring(not failed)) -text=text..string.format("=========================") -self:I(self.lid..text) -end -if failed then -self:I(self.lid..string.format("Mission %d [%s] failed!",self.auftragsnummer,self.type)) -if self.chief then -self.chief.Nfailure=self.chief.Nfailure+1 -end -self:Failed() -else -self:I(self.lid..string.format("Mission %d [%s] success!",self.auftragsnummer,self.type)) -if self.chief then -self.chief.Nsuccess=self.chief.Nsuccess+1 -end -self:Success() -end -return self -end -function AUFTRAG:GetOpsGroups() -local opsgroups={} -for _,_groupdata in pairs(self.groupdata or{})do -local groupdata=_groupdata -table.insert(opsgroups,groupdata.opsgroup) -end -return opsgroups -end -function AUFTRAG:GetAssetDataByName(AssetName) -return self.groupdata[tostring(AssetName)] -end -function AUFTRAG:GetGroupData(opsgroup) -if opsgroup and self.groupdata then -return self.groupdata[opsgroup.groupname] -end -return nil -end -function AUFTRAG:SetGroupStatus(opsgroup,status) -local oldstatus=self:GetGroupStatus(opsgroup) -self:T(self.lid..string.format("Setting OPSGROUP %s to status %s-->%s",opsgroup and opsgroup.groupname or"nil",tostring(oldstatus),tostring(status))) -if oldstatus==AUFTRAG.GroupStatus.CANCELLED and status==AUFTRAG.GroupStatus.DONE then -else -local groupdata=self:GetGroupData(opsgroup) -if groupdata then -groupdata.status=status -else -self:T(self.lid.."WARNING: Could not SET flight data for flight group. Setting status to DONE") -end -end -local isNotOver=self:IsNotOver() -local groupsDone=self:CheckGroupsDone() -self:T2(self.lid..string.format("Setting OPSGROUP %s status to %s. IsNotOver=%s CheckGroupsDone=%s",opsgroup.groupname,self:GetGroupStatus(opsgroup),tostring(self:IsNotOver()),tostring(groupsDone))) -if isNotOver and groupsDone then -self:T3(self.lid.."All assigned OPSGROUPs done ==> mission DONE!") -self:Done() -else -self:T3(self.lid.."Mission NOT DONE yet!") -end -return self -end -function AUFTRAG:GetGroupStatus(opsgroup) -self:T3(self.lid..string.format("Trying to get Flight status for flight group %s",opsgroup and opsgroup.groupname or"nil")) -local groupdata=self:GetGroupData(opsgroup) -if groupdata then -return groupdata.status -else -self:T(self.lid..string.format("WARNING: Could not GET groupdata for opsgroup %s. Returning status DONE.",opsgroup and opsgroup.groupname or"nil")) -return AUFTRAG.GroupStatus.DONE -end -end -function AUFTRAG:AddLegion(Legion) -self:T(self.lid..string.format("Adding legion %s",Legion.alias)) -table.insert(self.legions,Legion) -return self -end -function AUFTRAG:RemoveLegion(Legion) -for i=#self.legions,1,-1 do -local legion=self.legions[i] -if legion.alias==Legion.alias then -self:T(self.lid..string.format("Removing legion %s",Legion.alias)) -table.remove(self.legions,i) -self.statusLegion[Legion.alias]=nil -return self -end -end -self:T(self.lid..string.format("ERROR: Legion %s not found and could not be removed!",Legion.alias)) -return self -end -function AUFTRAG:SetLegionStatus(Legion,Status) -local status=self:GetLegionStatus(Legion) -self:T(self.lid..string.format("Setting LEGION %s to status %s-->%s",Legion.alias,tostring(status),tostring(Status))) -self.statusLegion[Legion.alias]=Status -return self -end -function AUFTRAG:GetLegionStatus(Legion) -local status=self.statusLegion[Legion.alias]or"unknown" -return status -end -function AUFTRAG:SetGroupWaypointCoordinate(opsgroup,coordinate) -local groupdata=self:GetGroupData(opsgroup) -if groupdata then -groupdata.waypointcoordinate=coordinate -end -return self -end -function AUFTRAG:GetGroupWaypointCoordinate(opsgroup) -local groupdata=self:GetGroupData(opsgroup) -if groupdata then -return groupdata.waypointcoordinate -end -end -function AUFTRAG:SetGroupWaypointTask(opsgroup,task) -self:T2(self.lid..string.format("Setting waypoint task %s",task and task.description or"WTF")) -local groupdata=self:GetGroupData(opsgroup) -if groupdata then -groupdata.waypointtask=task -end -end -function AUFTRAG:GetGroupWaypointTask(opsgroup) -local groupdata=self:GetGroupData(opsgroup) -if groupdata then -return groupdata.waypointtask -end -end -function AUFTRAG:SetGroupWaypointIndex(opsgroup,waypointindex) -self:T2(self.lid..string.format("Setting Mission waypoint UID=%d",waypointindex)) -local groupdata=self:GetGroupData(opsgroup) -if groupdata then -groupdata.waypointindex=waypointindex -end -return self -end -function AUFTRAG:GetGroupWaypointIndex(opsgroup) -local groupdata=self:GetGroupData(opsgroup) -if groupdata then -return groupdata.waypointindex -end -end -function AUFTRAG:SetGroupEgressWaypointUID(opsgroup,waypointindex) -self:T2(self.lid..string.format("Setting Egress waypoint UID=%d",waypointindex)) -local groupdata=self:GetGroupData(opsgroup) -if groupdata then -groupdata.waypointEgressUID=waypointindex -end -return self -end -function AUFTRAG:GetGroupEgressWaypointUID(opsgroup) -local groupdata=self:GetGroupData(opsgroup) -if groupdata then -return groupdata.waypointEgressUID -end -end -function AUFTRAG:CheckGroupsDone() -for groupname,data in pairs(self.groupdata)do -local groupdata=data -if groupdata then -if not(groupdata.status==AUFTRAG.GroupStatus.DONE or groupdata.status==AUFTRAG.GroupStatus.CANCELLED)then -self:T2(self.lid..string.format("CheckGroupsDone: OPSGROUP %s is not DONE or CANCELLED but in state %s. Mission NOT DONE!",groupdata.opsgroup.groupname,groupdata.status:upper())) -return false -end -end -end -for _,_legion in pairs(self.legions)do -local legion=_legion -local status=self:GetLegionStatus(legion) -if not status==AUFTRAG.Status.CANCELLED then -self:T2(self.lid..string.format("CheckGroupsDone: LEGION %s is not CANCELLED but in state %s. Mission NOT DONE!",legion.alias,status)) -return false -end -end -if self.commander then -if not self.statusCommander==AUFTRAG.Status.CANCELLED then -self:T2(self.lid..string.format("CheckGroupsDone: COMMANDER is not CANCELLED but in state %s. Mission NOT DONE!",self.statusCommander)) -return false -end -end -if self.chief then -if not self.statusChief==AUFTRAG.Status.CANCELLED then -self:T2(self.lid..string.format("CheckGroupsDone: CHIEF is not CANCELLED but in state %s. Mission NOT DONE!",self.statusChief)) -return false -end -end -if self:IsPlanned()or self:IsQueued()or self:IsRequested()then -self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] (PLANNED or QUEUED or REQUESTED). Mission NOT DONE!",self.status,self:GetState())) -return false -end -if self:IsExecuting()and self:_IsReinforcing()then -self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] and reinfoce=%d. Mission NOT DONE!",self.status,self:GetState(),self.reinforce)) -return false -end -if self:IsStarted()and self:CountOpsGroups()==0 then -self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] but count of alive OPSGROUP is zero. Mission DONE!",self.status,self:GetState())) -return true -end -return true -end -function AUFTRAG:OnEventUnitLost(EventData) -if EventData and EventData.IniGroup and EventData.IniUnit then -local unit=EventData.IniUnit -local group=EventData.IniGroup -local unitname=EventData.IniUnitName -for _,_groupdata in pairs(self.groupdata)do -local groupdata=_groupdata -if groupdata and groupdata.opsgroup and groupdata.opsgroup.groupname==EventData.IniGroupName then -self:T(self.lid..string.format("UNIT LOST event for opsgroup %s unit %s",groupdata.opsgroup.groupname,EventData.IniUnitName)) -end -end -end -end -function AUFTRAG:onafterPlanned(From,Event,To) -self.status=AUFTRAG.Status.PLANNED -self:T(self.lid..string.format("New mission status=%s",self.status)) -end -function AUFTRAG:onafterQueued(From,Event,To,Airwing) -self.status=AUFTRAG.Status.QUEUED -self:T(self.lid..string.format("New mission status=%s",self.status)) -end -function AUFTRAG:onafterRequested(From,Event,To) -self.status=AUFTRAG.Status.REQUESTED -self:T(self.lid..string.format("New mission status=%s",self.status)) -end -function AUFTRAG:onafterAssign(From,Event,To) -self.status=AUFTRAG.Status.ASSIGNED -self:T(self.lid..string.format("New mission status=%s",self.status)) -end -function AUFTRAG:onafterScheduled(From,Event,To) -self.status=AUFTRAG.Status.SCHEDULED -self:T(self.lid..string.format("New mission status=%s",self.status)) -end -function AUFTRAG:onafterStarted(From,Event,To) -self.status=AUFTRAG.Status.STARTED -self.Tstarted=timer.getAbsTime() -self:T(self.lid..string.format("New mission status=%s",self.status)) -end -function AUFTRAG:onafterExecuting(From,Event,To) -self.status=AUFTRAG.Status.EXECUTING -self.Texecuting=timer.getAbsTime() -self:T(self.lid..string.format("New mission status=%s",self.status)) -end -function AUFTRAG:onafterElementDestroyed(From,Event,To,OpsGroup,Element) -self.Ncasualties=self.Ncasualties+1 -end -function AUFTRAG:onafterGroupDead(From,Event,To,OpsGroup) -local asset=self:GetAssetByName(OpsGroup.groupname) -if asset then -self:AssetDead(asset) -end -self.Ndead=self.Ndead+1 -end -function AUFTRAG:onafterAssetDead(From,Event,To,Asset) -local N=self:CountOpsGroups() -local notreinforcing=self:_IsNotReinforcing() -self:T(self.lid..string.format("Asset %s dead! Number of ops groups remaining %d (reinforcing=%s)",tostring(Asset.spawngroupname),N,tostring(not notreinforcing))) -if N==0 and notreinforcing then -if self:IsNotOver()then -self:Cancel() -else -end -end -self:DelAsset(Asset) -end -function AUFTRAG:onafterCancel(From,Event,To) -local Ngroups=self:CountOpsGroups() -self:T(self.lid..string.format("CANCELLING mission in status %s. Will wait for %d groups to report mission DONE before evaluation",self.status,Ngroups)) -self.Tover=timer.getAbsTime() -self.Nrepeat=self.repeated -self.NrepeatFailure=self.repeatedFailure -self.NrepeatSuccess=self.repeatedSuccess -self.dTevaluate=0 -if self.chief then -self:T(self.lid..string.format("CHIEF will cancel the mission. Will wait for mission DONE before evaluation!")) -self.chief:MissionCancel(self) -elseif self.commander then -self:T(self.lid..string.format("COMMANDER will cancel the mission. Will wait for mission DONE before evaluation!")) -self.commander:MissionCancel(self) -elseif self.legions and#self.legions>0 then -for _,_legion in pairs(self.legions or{})do -local legion=_legion -self:T(self.lid..string.format("LEGION %s will cancel the mission. Will wait for mission DONE before evaluation!",legion.alias)) -legion:MissionCancel(self) -end -else -self:T(self.lid..string.format("No legion, commander or chief. Attached groups will cancel the mission on their own. Will wait for mission DONE before evaluation!")) -for _,_groupdata in pairs(self.groupdata or{})do -local groupdata=_groupdata -groupdata.opsgroup:MissionCancel(self) -end -end -if self:IsPlanned()or self:IsQueued()or self:IsRequested()or Ngroups==0 then -self:T(self.lid..string.format("Cancelled mission was in %s stage with %d groups assigned and alive. Call it done!",self.status,Ngroups)) -self:Done() -end -end -function AUFTRAG:onafterDone(From,Event,To) -self.status=AUFTRAG.Status.DONE -self:T(self.lid..string.format("New mission status=%s",self.status)) -self.Tover=timer.getAbsTime() -self.Texecuting=nil -self.statusChief=AUFTRAG.Status.DONE -self.statusCommander=AUFTRAG.Status.DONE -for _,_legion in pairs(self.legions)do -local Legion=_legion -self:SetLegionStatus(Legion,AUFTRAG.Status.DONE) -if self.type==AUFTRAG.Type.RELOCATECOHORT then -local requestid=self.requestID[Legion.alias] -if requestid then -self:T(self.lid.."Removing request from pending queue") -Legion:_DeleteQueueItemByID(requestid,Legion.pending) -local Cohort=self.DCStask.params.cohort -Legion:DelCohort(Cohort) -else -self:E(self.lid.."WARNING: Could NOT remove relocation request from from pending queue (all assets were spawned?)") -end -end -end -if self.type==AUFTRAG.Type.RELOCATECOHORT then -local cohort=self.DCStask.params.cohort -cohort:Relocated() -end -end -function AUFTRAG:onafterSuccess(From,Event,To) -self.status=AUFTRAG.Status.SUCCESS -self:T(self.lid..string.format("New mission status=%s",self.status)) -self.statusChief=self.status -self.statusCommander=self.status -for _,_legion in pairs(self.legions)do -local Legion=_legion -self:SetLegionStatus(Legion,self.status) -end -local repeatme=self.repeatedSuccess Repeat mission!",self.repeated+1,N)) -self:Repeat() -else -self:T(self.lid..string.format("Mission SUCCESS! Number of max repeats %d reached ==> Stopping mission!",self.repeated+1)) -self:Stop() -end -end -function AUFTRAG:onafterFailed(From,Event,To) -self.status=AUFTRAG.Status.FAILED -self:T(self.lid..string.format("New mission status=%s",self.status)) -self.statusChief=self.status -self.statusCommander=self.status -for _,_legion in pairs(self.legions)do -local Legion=_legion -self:SetLegionStatus(Legion,self.status) -end -local repeatme=self.repeatedFailure Repeat mission!",self.repeated+1,N)) -self:Repeat() -else -self:T(self.lid..string.format("Mission FAILED! Number of max repeats %d reached ==> Stopping mission!",self.repeated+1)) -self:Stop() -end -end -function AUFTRAG:onbeforeRepeat(From,Event,To) -if not(self.chief or self.commander or#self.legions>0)then -self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, COMMANDER or LEGION! Stopping AUFTRAG") -self:Stop() -return false -end -return true -end -function AUFTRAG:onafterRepeat(From,Event,To) -self.status=AUFTRAG.Status.PLANNED -self:T(self.lid..string.format("New mission status=%s (on Repeat)",self.status)) -self.statusChief=self.status -self.statusCommander=self.status -for _,_legion in pairs(self.legions)do -local Legion=_legion -self:SetLegionStatus(Legion,self.status) -end -self.repeated=self.repeated+1 -if self.chief then -self.statusChief=AUFTRAG.Status.PLANNED -if self.commander then -self.statusCommander=AUFTRAG.Status.PLANNED -end -for _,_legion in pairs(self.legions)do -local legion=_legion -legion:RemoveMission(self) -end -elseif self.commander then -self.statusCommander=AUFTRAG.Status.PLANNED -for _,_legion in pairs(self.legions)do -local legion=_legion -legion:RemoveMission(self) -self:SetLegionStatus(legion,AUFTRAG.Status.PLANNED) -end -elseif#self.legions>0 then -for _,_legion in pairs(self.legions)do -local legion=_legion -legion:RemoveMission(self) -self:SetLegionStatus(legion,AUFTRAG.Status.PLANNED) -legion:AddMission(self) -end -else -self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, COMMANDER or LEGION! Stopping AUFTRAG") -self:Stop() -return -end -self.assets={} -for groupname,_groupdata in pairs(self.groupdata)do -local groupdata=_groupdata -local opsgroup=groupdata.opsgroup -if opsgroup then -self:DelOpsGroup(opsgroup) -end -end -self.groupdata={} -self.Ncasualties=0 -self.Nelements=0 -self.Ngroups=0 -self.Nassigned=nil -self.Ndead=0 -self.DCStask=self:GetDCSMissionTask() -self:__Status(-30) -end -function AUFTRAG:onafterStop(From,Event,To) -self:T(self.lid..string.format("STOPPED mission in status=%s. Removing missions from queues. Stopping CallScheduler!",self.status)) -if self.chief then -self.chief:RemoveMission(self) -end -if self.commander then -self.commander:RemoveMission(self) -end -if#self.legions>0 then -for _,_legion in pairs(self.legions)do -local legion=_legion -legion:RemoveMission(self) -end -end -for _,_groupdata in pairs(self.groupdata)do -local groupdata=_groupdata -groupdata.opsgroup:RemoveMission(self) -end -self.assets={} -self.groupdata={} -self.CallScheduler:Clear() -end -function AUFTRAG:_TargetFromObject(Object) -if not self.engageTarget then -if Object and Object:IsInstanceOf("TARGET")then -self.engageTarget=Object -else -self.engageTarget=TARGET:New(Object) -end -else -end -return self -end -function AUFTRAG:CountMissionTargets() -local N=0 -local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition,true)or nil -if self.engageTarget then -N=self.engageTarget:CountTargets(Coalitions) -end -return N -end -function AUFTRAG:GetTargetInitialNumber() -local target=self:GetTargetData() -if target then -return target.N0 -else -return 0 -end -end -function AUFTRAG:GetTargetInitialLife() -local target=self:GetTargetData() -if target then -return target.life0 -else -return 0 -end -end -function AUFTRAG:GetTargetDamage() -local target=self:GetTargetData() -if target then -return target:GetDamage() -else -return 0 -end -end -function AUFTRAG:GetTargetLife() -local target=self:GetTargetData() -if target then -return target:GetLife() -else -return 0 -end -end -function AUFTRAG:GetTargetData() -return self.engageTarget -end -function AUFTRAG:GetObjective(RefCoordinate,Coalitions) -local objective=self:GetTargetData():GetObject(RefCoordinate,Coalitions) -return objective -end -function AUFTRAG:GetTargetType() -local target=self.engageTarget -if target then -local to=target:GetObjective() -if to then -return to.Type -else -return"Unknown" -end -else -return"Unknown" -end -end -function AUFTRAG:GetTargetVec2() -local coord=self:GetTargetCoordinate() -if coord then -local vec2=coord:GetVec2() -return vec2 -end -return nil -end -function AUFTRAG:GetTargetCoordinate() -if self.transportPickup then -return self.transportPickup -elseif self.missionZoneSet and self.type==AUFTRAG.Type.RECON then -return self.missionZoneSet:GetAverageCoordinate() -elseif self.engageTarget then -local coord=self.engageTarget:GetCoordinate() -return coord -elseif self.type==AUFTRAG.Type.ALERT5 then -return nil -else -self:T(self.lid.."ERROR: Cannot get target coordinate!") -end -return nil -end -function AUFTRAG:GetTargetHeading() -if self.engageTarget then -local heading=self.engageTarget:GetHeading() -return heading -end -return nil -end -function AUFTRAG:GetTargetName() -if self.engageTarget then -local name=self.engageTarget:GetName() -return name -end -return"N/A" -end -function AUFTRAG:GetTargetDistance(FromCoord) -local TargetCoord=self:GetTargetCoordinate() -if TargetCoord and FromCoord then -return TargetCoord:Get2DDistance(FromCoord) -else -self:T(self.lid.."ERROR: TargetCoord or FromCoord does not exist in AUFTRAG:GetTargetDistance() function! Returning 0") -end -return 0 -end -function AUFTRAG:AddAsset(Asset) -self:T(self.lid..string.format("Adding asset \"%s\" to mission",tostring(Asset.spawngroupname))) -self.assets=self.assets or{} -local asset=self:GetAssetByName(Asset.spawngroupname) -if not asset then -table.insert(self.assets,Asset) -self.Nassigned=self.Nassigned or 0 -self.Nassigned=self.Nassigned+1 -end -return self -end -function AUFTRAG:_AddAssets(Assets) -for _,asset in pairs(Assets)do -self:AddAsset(asset) -end -return self -end -function AUFTRAG:DelAsset(Asset) -for i,_asset in pairs(self.assets or{})do -local asset=_asset -if asset.uid==Asset.uid then -self:T(self.lid..string.format("Removing asset \"%s\" from mission",tostring(Asset.spawngroupname))) -table.remove(self.assets,i) -return self -end -end -return self -end -function AUFTRAG:GetAssetByName(Name) -for i,_asset in pairs(self.assets or{})do -local asset=_asset -if asset.spawngroupname==Name then -return asset -end -end -return nil -end -function AUFTRAG:CountOpsGroups() -local N=0 -for _,_groupdata in pairs(self.groupdata)do -local groupdata=_groupdata -if groupdata and groupdata.opsgroup and groupdata.opsgroup:IsAlive()and not groupdata.opsgroup:IsDead()then -N=N+1 -end -end -return N -end -function AUFTRAG:CountOpsGroupsInStatus(Status) -local N=0 -for _,_groupdata in pairs(self.groupdata)do -local groupdata=_groupdata -if groupdata and groupdata.status==Status then -N=N+1 -end -end -return N -end -function AUFTRAG:GetMissionTypesText(MissionTypes) -local text="" -for _,missiontype in pairs(MissionTypes)do -text=text..string.format("%s, ",missiontype) -end -return text -end -function AUFTRAG:SetMissionWaypointCoord(Coordinate) -if Coordinate:IsInstanceOf("ZONE_BASE")then -Coordinate=Coordinate:GetCoordinate() -end -self.missionWaypointCoord=Coordinate -return self -end -function AUFTRAG:SetMissionWaypointRandomization(Radius) -self.missionWaypointRadius=Radius -return self -end -function AUFTRAG:SetMissionEgressCoord(Coordinate,Altitude) -if Coordinate:IsInstanceOf("ZONE_BASE")then -Coordinate=Coordinate:GetCoordinate() -end -self.missionEgressCoord=Coordinate -if Altitude then -self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude) -end -end -function AUFTRAG:GetMissionEgressCoord() -return self.missionEgressCoord -end -function AUFTRAG:_GetMissionWaypointCoordSet() -if self.missionWaypointCoord then -local coord=self.missionWaypointCoord -if self.missionAltitude then -coord.y=self.missionAltitude -end -return coord -end -end -function AUFTRAG:GetMissionWaypointCoord(group,randomradius,surfacetypes) -if self.missionWaypointCoord then -local coord=self.missionWaypointCoord -if self.missionAltitude then -coord.y=self.missionAltitude -end -return coord -end -local waypointcoord=COORDINATE:New(0,0,0) -local coord=group:GetCoordinate() -if coord then -waypointcoord=coord:GetIntermediateCoordinate(self:GetTargetCoordinate(),self.missionFraction) -else -self:E(self.lid..string.format("ERROR: Cannot get coordinate of group %s (alive=%s)!",tostring(group:GetName()),tostring(group:IsAlive()))) -end -local alt=waypointcoord.y -if randomradius then -waypointcoord=ZONE_RADIUS:New("Temp",waypointcoord:GetVec2(),randomradius):GetRandomCoordinate(nil,nil,surfacetypes):SetAltitude(alt,false) -end -if self.missionAltitude then -waypointcoord:SetAltitude(self.missionAltitude,true) -end -return waypointcoord -end -function AUFTRAG:_SetLogID() -self.lid=string.format("Auftrag #%d %s | ",self.auftragsnummer,tostring(self.type)) -return self -end -function AUFTRAG:_GetRequestID(Legion) -local requestid=nil -local name=nil -if type(Legion)=="string"then -name=Legion -else -name=Legion.alias -end -if name then -requestid=self.requestID[name] -end -return nil -end -function AUFTRAG:_GetRequest(Legion) -local request=nil -local requestID=self:_GetRequestID(Legion) -if requestID then -request=Legion:GetRequestByID(requestID) -end -return request -end -function AUFTRAG:_SetRequestID(Legion,RequestID) -local requestid=nil -local name=nil -if type(Legion)=="string"then -name=Legion -else -name=Legion.alias -end -if name then -if self.requestID[name]then -self:I(self.lid..string.format("WARNING: Mission already has a request ID=%d!",self.requestID[name])) -end -self.requestID[name]=RequestID -end -return self -end -function AUFTRAG:_IsNotReinforcing() -local Nassigned=self.Nassigned and self.Nassigned-self.Ndead or 0 -local notreinforcing=((not self.reinforce)or(self.reinforce==0 and Nassigned<=0)) -return notreinforcing -end -function AUFTRAG:_IsReinforcing() -local reinforcing=not self:_IsNotReinforcing() -return reinforcing -end -function AUFTRAG:UpdateMarker() -local text=string.format("%s %s: %s",self.name,self.type:upper(),self.status:upper()) -text=text..string.format("\n%s",self:GetTargetName()) -text=text..string.format("\nTargets %d/%d, Life Points=%d/%d",self:CountMissionTargets(),self:GetTargetInitialNumber(),self:GetTargetLife(),self:GetTargetInitialLife()) -text=text..string.format("\nOpsGroups %d/%d",self:CountOpsGroups(),self:GetNumberOfRequiredAssets()) -if not self.marker then -local targetcoord=self:GetTargetCoordinate() -if targetcoord then -if self.markerCoaliton and self.markerCoaliton>=0 then -self.marker=MARKER:New(targetcoord,text):ReadOnly():ToCoalition(self.markerCoaliton) -else -self.marker=MARKER:New(targetcoord,text):ReadOnly():ToAll() -end -end -else -if self.marker:GetText()~=text then -self.marker:UpdateText(text) -end -end -return self -end -function AUFTRAG:GetDCSMissionTask() -local DCStasks={} -if self.type==AUFTRAG.Type.ANTISHIP then -local DCStask=CONTROLLABLE.EnRouteTaskAntiShip(nil) -table.insert(self.enrouteTasks,DCStask) -self:_GetDCSAttackTask(self.engageTarget,DCStasks) -elseif self.type==AUFTRAG.Type.AWACS then -local DCStask=CONTROLLABLE.EnRouteTaskAWACS(nil) -table.insert(self.enrouteTasks,DCStask) -elseif self.type==AUFTRAG.Type.BAI then -self:_GetDCSAttackTask(self.engageTarget,DCStasks) -elseif self.type==AUFTRAG.Type.BOMBING then -local DCStask=CONTROLLABLE.TaskBombing(nil,self:GetTargetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType,Divebomb) -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.BOMBRUNWAY then -local DCStask=CONTROLLABLE.TaskBombingRunway(nil,self.engageTarget:GetObject(),self.engageWeaponType,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAsGroup) -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.BOMBCARPET then -local DCStask=CONTROLLABLE.TaskCarpetBombing(nil,self:GetTargetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType,self.engageCarpetLength) -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.CAP then -local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil,self.engageZone:GetVec2(),self.engageZone:GetRadius(),self.engageTargetTypes,Priority) -table.insert(self.enrouteTasks,DCStask) -elseif self.type==AUFTRAG.Type.CAS then -local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil,self.engageZone:GetVec2(),self.engageZone:GetRadius(),self.engageTargetTypes,Priority) -table.insert(self.enrouteTasks,DCStask) -elseif self.type==AUFTRAG.Type.ESCORT then -local DCStask=CONTROLLABLE.TaskEscort(nil,self.engageTarget:GetObject(),self.escortVec3,nil,self.engageMaxDistance,self.engageTargetTypes) -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.GROUNDESCORT then -local DCSTask=CONTROLLABLE.TaskGroundEscort(nil,self.engageTarget:GetObject(),nil,self.orbitDistance,self.engageTargetTypes) -table.insert(DCStasks,DCSTask) -elseif self.type==AUFTRAG.Type.FACA then -local DCStask=CONTROLLABLE.TaskFAC_AttackGroup(nil,self.engageTarget:GetObject(),self.engageWeaponType,self.facDesignation,self.facDatalink,self.facFreq,self.facModu,CallsignName,CallsignNumber) -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.FAC then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.PATROLZONE -local param={} -param.zone=self:GetObjective() -param.altitude=self.missionAltitude -param.speed=self.missionSpeed -DCStask.params=param -table.insert(DCStasks,DCStask) -local DCSenroute=CONTROLLABLE.EnRouteTaskFAC(self,self.facFreq,self.facModu) -table.insert(self.enrouteTasks,DCSenroute) -elseif self.type==AUFTRAG.Type.FERRY then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.FERRY -local param={} -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.RELOCATECOHORT then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.RELOCATECOHORT -local param={} -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.INTERCEPT then -self:_GetDCSAttackTask(self.engageTarget,DCStasks) -elseif self.type==AUFTRAG.Type.ORBIT then -elseif self.type==AUFTRAG.Type.GCICAP then -elseif self.type==AUFTRAG.Type.RECON then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.RECON -local param={} -param.target=self.engageTarget -param.altitude=self.missionAltitude -param.speed=self.missionSpeed -param.lastindex=nil -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.SEAD then -self:_GetDCSAttackTask(self.engageTarget,DCStasks) -elseif self.type==AUFTRAG.Type.STRIKE then -local DCStask=CONTROLLABLE.TaskAttackMapObject(nil,self:GetTargetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType) -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.TANKER or self.type==AUFTRAG.Type.RECOVERYTANKER then -local DCStask=CONTROLLABLE.EnRouteTaskTanker(nil) -table.insert(self.enrouteTasks,DCStask) -elseif self.type==AUFTRAG.Type.TROOPTRANSPORT then -local TaskEmbark=CONTROLLABLE.TaskEmbarking(TaskControllable,self.transportPickup,self.transportGroupSet,self.transportWaitForCargo) -local TaskDisEmbark=CONTROLLABLE.TaskDisembarking(TaskControllable,self.transportDropoff,self.transportGroupSet) -table.insert(DCStasks,TaskEmbark) -table.insert(DCStasks,TaskDisEmbark) -elseif self.type==AUFTRAG.Type.OPSTRANSPORT then -local DCStask={} -DCStask.id="OpsTransport" -local param={} -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.CARGOTRANSPORT then -local TaskCargoTransportation={ -id="CargoTransportation", -params={} -} -table.insert(DCStasks,TaskCargoTransportation) -elseif self.type==AUFTRAG.Type.RESCUEHELO then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.FORMATION -local param={} -param.unitname=self:GetTargetName() -param.offsetX=200 -param.offsetZ=240 -param.altitude=70 -param.dtFollow=1.0 -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.ARTY then -if self.artyShots==1 or self.artyRadius<10 or true then -local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,self:GetTargetVec2(),self.artyRadius,self.artyShots,self.engageWeaponType,self.artyAltitude) -table.insert(DCStasks,DCStask) -else -local Vec2=self:GetTargetVec2() -local zone=ZONE_RADIUS:New("temp",Vec2,self.artyRadius) -for i=1,self.artyShots do -local vec2=zone:GetRandomVec2() -local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,vec2,0,1,self.engageWeaponType,self.artyAltitude) -table.insert(DCStasks,DCStask) -end -end -elseif self.type==AUFTRAG.Type.BARRAGE then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.BARRAGE -local param={} -param.zone=self:GetObjective() -param.altitude=self.artyAltitude -param.radius=self.artyRadius -param.heading=self.artyHeading -param.angle=self.artyAngle -param.shots=self.artyShots -param.weaponTypoe=self.engageWeaponType -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.PATROLZONE then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.PATROLZONE -local param={} -param.zone=self:GetObjective() -param.altitude=self.missionAltitude -param.speed=self.missionSpeed -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.CAPTUREZONE then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.CAPTUREZONE -local param={} -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.CASENHANCED then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.PATROLZONE -local param={} -param.zone=self:GetObjective() -param.altitude=self.missionAltitude -param.speed=self.missionSpeed -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.GROUNDATTACK then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.GROUNDATTACK -local param={} -param.target=self:GetTargetData() -param.action="Wedge" -param.speed=self.missionSpeed -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.AMMOSUPPLY then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.AMMOSUPPLY -local param={} -param.zone=self:GetObjective() -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.FUELSUPPLY then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.FUELSUPPLY -local param={} -param.zone=self:GetObjective() -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.REARMING then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.REARMING -local param={} -param.zone=self:GetObjective() -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.ALERT5 then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.ALERT5 -local param={} -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.NOTHING then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.NOTHING -local param={} -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.PATROLRACETRACK then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.PATROLRACETRACK -local param={} -param.TrackAltitude=self.TrackAltitude -param.TrackSpeed=self.TrackSpeed -param.TrackPoint1=self.TrackPoint1 -param.TrackPoint2=self.TrackPoint2 -param.missionSpeed=self.missionSpeed -param.missionAltitude=self.missionAltitude -param.TrackFormation=self.TrackFormation -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.HOVER then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.HOVER -local param={} -param.hoverAltitude=self.hoverAltitude -param.hoverTime=self.hoverTime -param.missionSpeed=self.missionSpeed -param.missionAltitude=self.missionAltitude -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.LANDATCOORDINATE then -local DCStask={} -local Vec2=self.stayAt:GetVec2() -local DCStask=CONTROLLABLE.TaskLandAtVec2(nil,Vec2,self.stayTime) -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.ONGUARD or self.type==AUFTRAG.Type.ARMOREDGUARD then -local DCStask={} -DCStask.id=self.type==AUFTRAG.Type.ONGUARD and AUFTRAG.SpecialTask.ONGUARD or AUFTRAG.SpecialTask.ARMOREDGUARD -local param={} -param.coordinate=self:GetObjective() -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.AIRDEFENSE then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.AIRDEFENSE -local param={} -param.zone=self:GetObjective() -DCStask.params=param -table.insert(DCStasks,DCStask) -elseif self.type==AUFTRAG.Type.EWR then -local DCStask={} -DCStask.id=AUFTRAG.SpecialTask.EWR -local param={} -param.zone=self:GetObjective() -DCStask.params=param -table.insert(DCStasks,DCStask) -local Enroutetask=CONTROLLABLE.EnRouteTaskEWR() -table.insert(self.enrouteTasks,Enroutetask) -else -self:T(self.lid..string.format("ERROR: Unknown mission task!")) -return nil -end -if self.type==AUFTRAG.Type.ORBIT or -self.type==AUFTRAG.Type.CAP or -self.type==AUFTRAG.Type.CAS or -self.type==AUFTRAG.Type.GCICAP or -self.type==AUFTRAG.Type.AWACS or -self.type==AUFTRAG.Type.TANKER or -self.type==AUFTRAG.Type.RECOVERYTANKER then -self.orbitVec2=self:GetTargetVec2() -if self.orbitVec2 then -self.targetHeading=self:GetTargetHeading() -local OffsetVec2=nil -if(self.orbitOffsetVec2~=nil)then -OffsetVec2=UTILS.DeepCopy(self.orbitOffsetVec2) -end -if OffsetVec2 then -if self.orbitOffsetVec2.r then -local r=self.orbitOffsetVec2.r -local phi=(self.orbitOffsetVec2.phi or 0)+self.targetHeading -OffsetVec2.x=r*math.cos(math.rad(phi)) -OffsetVec2.y=r*math.sin(math.rad(phi)) -else -OffsetVec2.x=self.orbitOffsetVec2.x -OffsetVec2.y=self.orbitOffsetVec2.y -end -end -local orbitVec2=OffsetVec2 and UTILS.Vec2Add(self.orbitVec2,OffsetVec2)or self.orbitVec2 -local orbitRaceTrack=nil -if self.orbitLeg then -local heading=0 -if self.orbitHeading then -if self.orbitHeadingRel then -heading=self.targetHeading+self.orbitHeading -else -heading=self.orbitHeading -end -else -heading=self.targetHeading or 0 -end -orbitRaceTrack=UTILS.Vec2Translate(orbitVec2,self.orbitLeg,heading) -end -local orbitRaceTrackCoord=nil -if orbitRaceTrack then -orbitRaceTrackCoord=COORDINATE:NewFromVec2(orbitRaceTrack) -end -local DCStask=CONTROLLABLE.TaskOrbit(nil,COORDINATE:NewFromVec2(orbitVec2),self.orbitAltitude,self.orbitSpeed,orbitRaceTrackCoord) -table.insert(DCStasks,DCStask) -end -end -self:T3({missiontask=DCStasks}) -if#DCStasks==1 then -return DCStasks[1] -else -return CONTROLLABLE.TaskCombo(nil,DCStasks) -end -end -function AUFTRAG:_GetDCSAttackTask(Target,DCStasks) -DCStasks=DCStasks or{} -for _,_target in pairs(Target.targets)do -local target=_target -if target.Type==TARGET.ObjectType.GROUP then -local DCStask=CONTROLLABLE.TaskAttackGroup(nil,target.Object,self.engageWeaponType,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageAsGroup) -table.insert(DCStasks,DCStask) -elseif target.Type==TARGET.ObjectType.UNIT or target.Type==TARGET.ObjectType.STATIC then -local DCStask=CONTROLLABLE.TaskAttackUnit(nil,target.Object,self.engageAsGroup,self.WeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType) -table.insert(DCStasks,DCStask) -end -end -return DCStasks -end -function AUFTRAG:GetMissionTaskforMissionType(MissionType) -local mtask=ENUMS.MissionTask.NOTHING -if MissionType==AUFTRAG.Type.ANTISHIP then -mtask=ENUMS.MissionTask.ANTISHIPSTRIKE -elseif MissionType==AUFTRAG.Type.AWACS then -mtask=ENUMS.MissionTask.AWACS -elseif MissionType==AUFTRAG.Type.BAI then -mtask=ENUMS.MissionTask.GROUNDATTACK -elseif MissionType==AUFTRAG.Type.BOMBCARPET then -mtask=ENUMS.MissionTask.GROUNDATTACK -elseif MissionType==AUFTRAG.Type.BOMBING then -mtask=ENUMS.MissionTask.GROUNDATTACK -elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then -mtask=ENUMS.MissionTask.RUNWAYATTACK -elseif MissionType==AUFTRAG.Type.CAP then -mtask=ENUMS.MissionTask.CAP -elseif MissionType==AUFTRAG.Type.GCICAP then -mtask=ENUMS.MissionTask.CAP -elseif MissionType==AUFTRAG.Type.CAS then -mtask=ENUMS.MissionTask.CAS -elseif MissionType==AUFTRAG.Type.PATROLZONE then -mtask=ENUMS.MissionTask.CAS -elseif MissionType==AUFTRAG.Type.CASENHANCED then -mtask=ENUMS.MissionTask.CAS -elseif MissionType==AUFTRAG.Type.ESCORT then -mtask=ENUMS.MissionTask.ESCORT -elseif MissionType==AUFTRAG.Type.FACA then -mtask=ENUMS.MissionTask.AFAC -elseif MissionType==AUFTRAG.Type.FAC then -mtask=ENUMS.MissionTask.AFAC -elseif MissionType==AUFTRAG.Type.FERRY then -mtask=ENUMS.MissionTask.NOTHING -elseif MissionType==AUFTRAG.Type.GROUNDESCORT then -mtask=ENUMS.MissionTask.GROUNDESCORT -elseif MissionType==AUFTRAG.Type.INTERCEPT then -mtask=ENUMS.MissionTask.INTERCEPT -elseif MissionType==AUFTRAG.Type.RECON then -mtask=ENUMS.MissionTask.RECONNAISSANCE -elseif MissionType==AUFTRAG.Type.SEAD then -mtask=ENUMS.MissionTask.SEAD -elseif MissionType==AUFTRAG.Type.STRIKE then -mtask=ENUMS.MissionTask.GROUNDATTACK -elseif MissionType==AUFTRAG.Type.TANKER then -mtask=ENUMS.MissionTask.REFUELING -elseif MissionType==AUFTRAG.Type.TROOPTRANSPORT then -mtask=ENUMS.MissionTask.TRANSPORT -elseif MissionType==AUFTRAG.Type.CARGOTRANSPORT then -mtask=ENUMS.MissionTask.TRANSPORT -elseif MissionType==AUFTRAG.Type.ARMORATTACK then -mtask=ENUMS.MissionTask.NOTHING -elseif MissionType==AUFTRAG.Type.HOVER then -mtask=ENUMS.MissionTask.NOTHING -elseif MissionType==AUFTRAG.Type.PATROLRACETRACK then -mtask=ENUMS.MissionTask.CAP -end -return mtask -end -function AUFTRAG.CheckMissionType(MissionType,PossibleTypes) -if type(PossibleTypes)=="string"then -PossibleTypes={PossibleTypes} -end -for _,canmission in pairs(PossibleTypes)do -if canmission==MissionType then -return true -end -end -return false -end -function AUFTRAG.CheckMissionCapability(MissionTypes,Capabilities,All) -if type(MissionTypes)~="table"then -MissionTypes={MissionTypes} -end -for _,cap in pairs(Capabilities)do -local capability=cap -for _,MissionType in pairs(MissionTypes)do -if All==true then -if capability.MissionType~=MissionType then -return false -end -else -if capability.MissionType==MissionType then -return true -end -end -end -end -if All==true then -return true -else -return false -end -end -function AUFTRAG.CheckMissionCapabilityAny(MissionTypes,Capabilities) -local res=AUFTRAG.CheckMissionCapability(MissionTypes,Capabilities,false) -return res -end -function AUFTRAG.CheckMissionCapabilityAll(MissionTypes,Capabilities) -local res=AUFTRAG.CheckMissionCapability(MissionTypes,Capabilities,true) -return res -end -do -AWACS={ -ClassName="AWACS", -version="0.2.61", -lid="", -coalition=coalition.side.BLUE, -coalitiontxt="blue", -OpsZone=nil, -StationZone=nil, -AirWing=nil, -Frequency=271, -Modulation=radio.modulation.AM, -Airbase=nil, -AwacsAngels=25, -OrbitZone=nil, -CallSign=CALLSIGN.AWACS.Magic, -CallSignNo=1, -debug=false, -verbose=false, -ManagedGrps={}, -ManagedGrpID=0, -ManagedTaskID=0, -AnchorStacks={}, -CAPIdleAI={}, -CAPIdleHuman={}, -TaskedCAPAI={}, -TaskedCAPHuman={}, -OpenTasks={}, -ManagedTasks={}, -PictureAO={}, -PictureEWR={}, -Contacts={}, -Countactcounter=0, -ContactsAO={}, -RadioQueue={}, -PrioRadioQueue={}, -TacticalQueue={}, -AwacsTimeOnStation=4, -AwacsTimeStamp=0, -EscortsTimeOnStation=4, -EscortsTimeStamp=0, -CAPTimeOnStation=4, -AwacsROE="", -AwacsROT="", -MenuStrict=true, -MaxAIonCAP=3, -AIonCAP=0, -AICAPMissions={}, -ShiftChangeAwacsFlag=false, -ShiftChangeEscortsFlag=false, -ShiftChangeAwacsRequested=false, -ShiftChangeEscortsRequested=false, -CAPAirwings={}, -MonitoringData={}, -MonitoringOn=false, -FlightGroups={}, -AwacsMission=nil, -AwacsInZone=false, -AwacsReady=false, -CatchAllMissions={}, -CatchAllFGs={}, -PictureInterval=300, -ReassignTime=120, -PictureTimeStamp=0, -BorderZone=nil, -RejectZone=nil, -maxassigndistance=100, -PlayerGuidance=true, -ModernEra=true, -callsignshort=true, -keepnumber=true, -callsignTranslations=nil, -TacDistance=45, -MeldDistance=35, -ThreatDistance=25, -AOName="Rock", -AOCoordinate=nil, -clientmenus=nil, -RadarBlur=15, -ReassignmentPause=180, -NoGroupTags=false, -SuppressScreenOutput=false, -NoMissileCalls=true, -GoogleTTSPadding=1, -WindowsTTSPadding=2.5, -PlayerCapAssignment=true, -AllowMarkers=false, -PlayerStationName=nil, -GCI=false, -GCIGroup=nil, -locale="en", -IncludeHelicopters=false, -TacticalMenu=false, -TacticalFrequencies={}, -TacticalSubscribers={}, -TacticalBaseFreq=130, -TacticalIncrFreq=0.5, -TacticalModulation=radio.modulation.AM, -TacticalInterval=120, -DetectionSet=nil, -} -AWACS.CallSignClear={ -[1]="Overlord", -[2]="Magic", -[3]="Wizard", -[4]="Focus", -[5]="Darkstar", -} -AWACS.AnchorNames={ -[1]="One", -[2]="Two", -[3]="Three", -[4]="Four", -[5]="Five", -[6]="Six", -[7]="Seven", -[8]="Eight", -[9]="Nine", -[10]="Ten", -} -AWACS.IFF= -{ -SPADES="Spades", -NEUTRAL="Neutral", -FRIENDLY="Friendly", -ENEMY="Hostile", -BOGEY="Bogey", -} -AWACS.Phonetic= -{ -[1]='Alpha', -[2]='Bravo', -[3]='Charlie', -[4]='Delta', -[5]='Echo', -[6]='Foxtrot', -[7]='Golf', -[8]='Hotel', -[9]='India', -[10]='Juliett', -[11]='Kilo', -[12]='Lima', -[13]='Mike', -[14]='November', -[15]='Oscar', -[16]='Papa', -[17]='Quebec', -[18]='Romeo', -[19]='Sierra', -[20]='Tango', -[21]='Uniform', -[22]='Victor', -[23]='Whiskey', -[24]='Xray', -[25]='Yankee', -[26]='Zulu', -} -AWACS.Shipsize= -{ -[1]="Singleton", -[2]="Two-Ship", -[3]="Heavy", -[4]="Gorilla", -} -AWACS.ROE={ -POLICE="Police", -VID="Visual ID", -IFF="IFF", -BVR="Beyond Visual Range", -} -AWACS.ROT={ -BYPASSESCAPE="Bypass and Escape", -EVADE="Evade Fire", -PASSIVE="Passive Defense", -RETURNFIRE="Return Fire", -OPENFIRE="Open Fire", -} -AWACS.THREATLEVEL={ -GREEN=3, -AMBER=7, -RED=10, -} -AWACS.CapVoices={ -[1]="de-DE-Wavenet-A", -[2]="de-DE-Wavenet-B", -[3]="fr-FR-Wavenet-A", -[4]="fr-FR-Wavenet-B", -[5]="en-GB-Wavenet-A", -[6]="en-GB-Wavenet-B", -[7]="en-GB-Wavenet-D", -[8]="en-AU-Wavenet-B", -[9]="en-US-Wavenet-J", -[10]="en-US-Wavenet-H", -} -AWACS.Messages={ -EN= -{ -DEFEND="%s, %s! %s! %s! Defend!", -VECTORTO="%s, %s. Vector%s %s", -VECTORTOTTS="%s, %s, Vector%s %s", -ANGELS=". Angels ", -ZERO="zero", -VANISHED="%s, %s Group. Vanished.", -VANISHEDTTS="%s, %s group vanished.", -SHIFTCHANGE="%s shift change for %s control.", -GROUPCAP="Group", -GROUP="group", -MILES="miles", -THOUSAND="thousand", -BOGEY="Bogey", -ALLSTATIONS="All Stations", -PICCLEAN="%s. %s. Picture Clean.", -PICTURE="Picture", -ONE="One", -GROUPMULTI="groups", -NOTCHECKEDIN="%s. %s. Negative. You are not checked in.", -CLEAN="%s. %s. Clean.", -DOPE="%s. %s. Bogey Dope. ", -VIDPOS="%s. %s. Copy, target identified as %s.", -VIDNEG="%s. %s. Negative, get closer to target.", -FFNEUTRAL="Neutral", -FFFRIEND="Friendly", -FFHOSTILE="Hostile", -FFSPADES="Spades", -FFCLEAN="Clean", -COPY="%s. %s. Copy.", -TARGETEDBY="Targeted by %s.", -STATUS="Status", -ALREADYCHECKEDIN="%s. %s. Negative. You are already checked in.", -ALPHACHECK="Alpha Check", -CHECKINAI="%s. %s. Checking in as fragged. Expected playtime %d hours. Request Alpha Check %s.", -SAFEFLIGHT="%s. %s. Copy. Have a safe flight home.", -VERYLOW="very low", -AIONSTATION="%s. %s. On station over anchor %d at angels %d. Ready for tasking.", -POPUP="Pop-up", -NEWGROUP="New group", -HIGH=" High.", -VERYFAST=" Very fast.", -FAST=" Fast.", -THREAT="Threat", -MERGED="Merged", -SCREENVID="Intercept and VID %s group.", -SCREENINTER="Intercept %s group.", -ENGAGETAG="Targeted by %s.", -REQCOMMIT="%s. %s group. %s. %s, request commit.", -AICOMMIT="%s. %s group. %s. %s, commit.", -COMMIT="Commit", -SUNRISE="%s. All stations, SUNRISE SUNRISE SUNRISE, %s.", -AWONSTATION="%s on station for %s control.", -STATIONAT="%s. %s. Station at %s at angels %d.", -STATIONATLONG="%s. %s. Station at %s at angels %d doing %d knots.", -STATIONSCREEN="%s. %s.\nStation at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s.", -STATIONTASK="Station at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s", -VECTORSTATION=" to Station", -TEXTOPTIONS1="Lost friendly flight", -TEXTOPTIONS2="Vanished friendly flight", -TEXTOPTIONS3="Faded friendly contact", -TEXTOPTIONS4="Lost contact with", -}, -} -AWACS.TaskDescription={ -ANCHOR="Anchor", -REANCHOR="Re-Anchor", -VID="VID", -IFF="IFF", -INTERCEPT="Intercept", -SWEEP="Sweep", -RTB="RTB", -} -AWACS.TaskStatus={ -IDLE="Idle", -UNASSIGNED="Unassigned", -REQUESTED="Requested", -ASSIGNED="Assigned", -EXECUTING="Executing", -SUCCESS="Success", -FAILED="Failed", -DEAD="Dead", -} -function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,StationZone,Frequency,Modulation) -local self=BASE:Inherit(self,FSM:New()) -if Coalition and type(Coalition)=="string"then -if Coalition=="blue"then -self.coalition=coalition.side.BLUE -self.coalitiontxt=Coalition -elseif Coalition=="red"then -self.coalition=coalition.side.RED -self.coalitiontxt=Coalition -elseif Coalition=="neutral"then -self.coalition=coalition.side.NEUTRAL -self.coalitiontxt=Coalition -else -self:E("ERROR: Unknown coalition in AWACS!") -end -else -self.coalition=Coalition -self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition)) -end -self.Name=Name -self.AirWing=AirWing -AirWing:SetUsingOpsAwacs(self) -self.CAPAirwings=FIFO:New() -self.CAPAirwings:Push(AirWing,1) -self.AwacsFG=nil -self.RadarBlur=15 -if type(OpsZone)=="string"then -self.OpsZone=ZONE:New(OpsZone) -elseif type(OpsZone)=="table"and OpsZone.ClassName and string.find(OpsZone.ClassName,"ZONE")then -self.OpsZone=OpsZone -else -self:E("AWACS - Invalid Zone passed!") -return -end -self.AOCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(self.coalition)) -self.AOName=self.OpsZone:GetName() -self.UseBullsAO=true -self.ControlZoneRadius=100 -self.StationZone=ZONE:New(StationZone) -self.StationZoneName=StationZone -self.Frequency=Frequency or 271 -self.Modulation=Modulation or radio.modulation.AM -self.MultiFrequency={self.Frequency} -self.MultiModulation={self.Modulation} -self.Airbase=AIRBASE:FindByName(AirbaseName) -self.AwacsAngels=25 -if AwacsOrbit then -self.OrbitZone=ZONE:New(AwacsOrbit) -end -self.BorderZone=nil -self.CallSign=CALLSIGN.AWACS.Magic -self.CallSignNo=1 -self.NoHelos=true -self.AIRequested=0 -self.AIonCAP=0 -self.AICAPMissions=FIFO:New() -self.FlightGroups=FIFO:New() -self.Countactcounter=0 -self.PictureInterval=300 -self.PictureTimeStamp=0 -self.ReassignTime=120 -self.intelstarted=false -self.sunrisedone=false -local speed=250 -self.SpeedBase=speed -self.Speed=speed -self.Heading=0 -self.Leg=50 -self.invisible=false -self.immortal=false -self.callsigntxt="AWACS" -self.AwacsTimeOnStation=4 -self.AwacsTimeStamp=0 -self.EscortsTimeOnStation=4 -self.EscortsTimeStamp=0 -self.ShiftChangeTime=0.25 -self.ShiftChangeAwacsFlag=false -self.ShiftChangeEscortsFlag=false -self.CapSpeedBase=270 -self.CAPTimeOnStation=4 -self.MaxAIonCAP=4 -self.AICAPCAllName=CALLSIGN.Aircraft.Colt -self.AICAPCAllNumber=0 -self.CAPGender="male" -self.CAPCulture="en-US" -self.CAPVoice=nil -self.AwacsMission=nil -self.AwacsInZone=false -self.AwacsReady=false -self.AwacsROE=AWACS.ROE.IFF -self.AwacsROT=AWACS.ROT.BYPASSESCAPE -self.HasEscorts=false -self.EscortTemplate="" -self.EscortMission={} -self.EscortMissionReplacement={} -self.PathToSRS="C:\\Program Files\\DCS-SimpleRadio-Standalone" -self.Gender="female" -self.Culture="en-GB" -self.Voice=nil -self.Port=5002 -self.Volume=1.0 -self.RadioQueue=FIFO:New() -self.PrioRadioQueue=FIFO:New() -self.TacticalQueue=FIFO:New() -self.maxspeakentries=3 -self.GoogleTTSPadding=1 -self.WindowsTTSPadding=2.5 -self.clientset=SET_CLIENT:New():FilterActive(true):FilterCoalitions(self.coalitiontxt):FilterCategories("plane"):FilterStart() -self.PlayerGuidance=true -self.ModernEra=true -self.NoGroupTags=false -self.SuppressScreenOutput=false -self.ReassignmentPause=180 -self.callsignshort=true -self.DeclareRadius=5 -self.MenuStrict=true -self.maxassigndistance=100 -self.NoMissileCalls=true -self.PlayerCapAssignment=true -self.ManagedGrps={} -self.ManagedGrpID=0 -self.callsignTranslations=nil -self.AnchorStacks=FIFO:New() -self.AnchorBaseAngels=22 -self.AnchorStackDistance=2 -self.AnchorMaxStacks=4 -self.AnchorMaxAnchors=2 -self.AnchorMaxZones=6 -self.AnchorCurrZones=1 -self.AnchorTurn=-(360/self.AnchorMaxZones) -self:_CreateAnchorStack() -self.ManagedTasks=FIFO:New() -local MonitoringData={} -MonitoringData.AICAPCurrent=0 -MonitoringData.AICAPMax=self.MaxAIonCAP -MonitoringData.Airwings=1 -MonitoringData.PlayersCheckedin=0 -MonitoringData.Players=0 -MonitoringData.AwacsShiftChange=false -MonitoringData.AwacsStateFG="unknown" -MonitoringData.AwacsStateMission="unknown" -MonitoringData.EscortsShiftChange=false -MonitoringData.EscortsStateFG={} -MonitoringData.EscortsStateMission={} -self.MonitoringOn=false -self.MonitoringData=MonitoringData -self.CatchAllMissions={} -self.CatchAllFGs={} -self.PictureAO=FIFO:New() -self.PictureEWR=FIFO:New() -self.Contacts=FIFO:New() -self.CID=0 -self.ContactsAO=FIFO:New() -self.clientmenus=FIFO:New() -self.TacticalMenu=false -self.TacticalBaseFreq=130 -self.TacticalIncrFreq=0.5 -self.TacticalModulation=radio.modulation.AM -self.acticalFrequencies={} -self.TacticalSubscribers={} -self.TacticalInterval=120 -self.DetectionSet=SET_GROUP:New() -self.lid=string.format("%s (%s) | ",self.Name,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","StartUp") -self:AddTransition("StartUp","Started","Running") -self:AddTransition("*","Status","*") -self:AddTransition("*","CheckedIn","*") -self:AddTransition("*","CheckedOut","*") -self:AddTransition("*","AssignAnchor","*") -self:AddTransition("*","AssignedAnchor","*") -self:AddTransition("*","ReAnchor","*") -self:AddTransition("*","NewCluster","*") -self:AddTransition("*","NewContact","*") -self:AddTransition("*","LostCluster","*") -self:AddTransition("*","LostContact","*") -self:AddTransition("*","CheckRadioQueue","*") -self:AddTransition("*","CheckTacticalQueue","*") -self:AddTransition("*","EscortShiftChange","*") -self:AddTransition("*","AwacsShiftChange","*") -self:AddTransition("*","FlightOnMission","*") -self:AddTransition("*","Intercept","*") -self:AddTransition("*","InterceptSuccess","*") -self:AddTransition("*","InterceptFailure","*") -self:AddTransition("*","Stop","Stopped") -local text=string.format("%sAWACS Version %s Initiated",self.lid,self.version) -self:I(text) -self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) -self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler) -self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler) -self:HandleEvent(EVENTS.Ejection,self._EventHandler) -self:HandleEvent(EVENTS.Crash,self._EventHandler) -self:HandleEvent(EVENTS.Dead,self._EventHandler) -self:HandleEvent(EVENTS.UnitLost,self._EventHandler) -self:HandleEvent(EVENTS.BDA,self._EventHandler) -self:HandleEvent(EVENTS.PilotDead,self._EventHandler) -self:HandleEvent(EVENTS.Shot,self._EventHandler) -self:_InitLocalization() -return self -end -function AWACS:SetTacticalRadios(BaseFreq,Increase,Modulation,Interval,Number) -self:T(self.lid.."SetTacticalRadios") -self.TacticalMenu=true -self.TacticalBaseFreq=BaseFreq or 130 -self.TacticalIncrFreq=Increase or 0.5 -self.TacticalModulation=Modulation or radio.modulation.AM -self.TacticalInterval=Interval or 120 -local number=Number or 10 -if number<1 then number=1 end -if number>10 then number=10 end -for i=1,number do -local freq=self.TacticalBaseFreq+((i-1)*self.TacticalIncrFreq) -self.TacticalFrequencies[freq]=freq -end -if self.AwacsSRS then -self.TacticalSRS=MSRS:New(self.PathToSRS,self.TacticalBaseFreq,self.TacticalModulation) -self.TacticalSRS:SetCoalition(self.coalition) -self.TacticalSRS:SetGender(self.Gender) -self.TacticalSRS:SetCulture(self.Culture) -self.TacticalSRS:SetVoice(self.Voice) -self.TacticalSRS:SetPort(self.Port) -self.TacticalSRS:SetLabel("AWACS") -self.TacticalSRS:SetVolume(self.Volume) -if self.PathToGoogleKey then -self.TacticalSRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey) -self.TacticalSRS:SetProvider(MSRS.Provider.GOOGLE) -end -self.TacticalSRSQ=MSRSQUEUE:New("Tactical AWACS") -end -return self -end -function AWACS:_RefreshMenuNonSubscribed() -self:T(self.lid.."_RefreshMenuNonSubscribed") -local aliveset=self.clientset:GetAliveSet() -for _,_group in pairs(aliveset)do -local grp=_group -local Group=grp:GetGroup() -local gname=nil -if Group and Group:IsAlive()then -gname=Group:GetName() -self:T(gname) -end -local menustr=self.clientmenus:ReadByID(gname) -local menu=menustr.tactical -if not self.TacticalSubscribers[gname]and menu then -menu:RemoveSubMenus() -for _,_freq in UTILS.spairs(self.TacticalFrequencies)do -local modu=UTILS.GetModulationName(self.TacticalModulation) -local text=string.format("Subscribe to %.3f %s",_freq,modu) -local entry=MENU_GROUP_COMMAND:New(Group,text,menu,self._SubScribeTactRadio,self,Group,_freq) -end -end -end -return self -end -function AWACS:_UnsubScribeTactRadio(Group) -self:T(self.lid.."_UnsubScribeTactRadio") -local text="" -local textScreen="" -local GID,Outcome=self:_GetManagedGrpID(Group) -local gcallsign=self:_GetCallSign(Group,GID)or"Ghost 1" -local gname=Group:GetName()or"unknown" -if Outcome and self.TacticalSubscribers[gname]then -local Freq=self.TacticalSubscribers[gname] -self.TacticalFrequencies[Freq]=Freq -self.TacticalSubscribers[gname]=nil -local modu=self.TacticalModulation==0 and"AM"or"FM" -text=string.format("%s, %s, switch back to AWACS main frequency!",gcallsign,self.callsigntxt) -self:_NewRadioEntry(text,text,GID,true,true,true,false,true) -self:_RefreshMenuNonSubscribed() -elseif self.AwacsFG then -local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) -end -return self -end -function AWACS:_SubScribeTactRadio(Group,Frequency) -self:T(self.lid.."_SubScribeTactRadio") -local text="" -local textScreen="" -local GID,Outcome=self:_GetManagedGrpID(Group) -local gcallsign=self:_GetCallSign(Group,GID)or"Ghost 1" -local gname=Group:GetName()or"unknown" -if Outcome then -self.TacticalSubscribers[gname]=Frequency -self.TacticalFrequencies[Frequency]=nil -local modu=self.TacticalModulation==0 and"AM"or"FM" -text=string.format("%s, %s, switch to %.3f %s for tactical information!",gcallsign,self.callsigntxt,Frequency,modu) -self:_NewRadioEntry(text,text,GID,true,true,true,false,true) -local menustr=self.clientmenus:ReadByID(gname) -local menu=menustr.tactical -if menu then -menu:RemoveSubMenus() -local text=string.format("Unsubscribe %.3f %s",Frequency,modu) -local entry=MENU_GROUP_COMMAND:New(Group,text,menu,self._UnsubScribeTactRadio,self,Group) -end -elseif self.AwacsFG then -local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) -end -return self -end -function AWACS:_CheckSubscribers() -self:T(self.lid.."_InitLocalization") -for _name,_freq in pairs(self.TacticalSubscribers or{})do -local grp=GROUP:FindByName(_name) -if(not grp)or(not grp:IsAlive())then -self.TacticalFrequencies[_freq]=_freq -self.TacticalSubscribers[_name]=nil -end -end -return self -end -function AWACS:_InitLocalization() -self:T(self.lid.."_InitLocalization") -self.gettext=TEXTANDSOUND:New("AWACS","en") -self.locale="en" -for locale,table in pairs(self.Messages)do -local Locale=string.lower(tostring(locale)) -self:T("**** Adding locale: "..Locale) -for ID,Text in pairs(table)do -self:T(string.format('Adding ID %s',tostring(ID))) -self.gettext:AddEntry(Locale,tostring(ID),Text) -end -end -return self -end -function AWACS:SetLocale(Locale) -self:T(self.lid.."SetLocale") -self.locale=Locale or"en" -return self -end -function AWACS:AddFrequencyAndModulation(Frequency,Modulation) -self:T(self.lid.."AddFrequencyAndModulation") -table.insert(self.MultiFrequency,Frequency) -table.insert(self.MultiModulation,Modulation) -if self.AwacsSRS then -self.AwacsSRS:SetFrequencies(self.MultiFrequency) -self.AwacsSRS:SetModulations(self.MultiModulation) -end -return self -end -function AWACS:SetAsGCI(EWR,Delay) -self:T(self.lid.."SetGCI") -local delay=Delay or-5 -if type(EWR)=="string"then -self.GCIGroup=GROUP:FindByName(EWR) -else -self.GCIGroup=EWR -end -self.GCI=true -self:SetEscort(0) -return self -end -function AWACS:_NewRadioEntry(TextTTS,TextScreen,GID,IsGroup,ToScreen,IsNew,FromAI,IsPrio,Tactical) -self:T(self.lid.."_NewRadioEntry") -local RadioEntry={} -RadioEntry.IsNew=IsNew -RadioEntry.TextTTS=TextTTS -RadioEntry.TextScreen=TextScreen or TextTTS -RadioEntry.GroupID=GID -RadioEntry.ToScreen=ToScreen -RadioEntry.Duration=STTS.getSpeechTime(TextTTS,0.95,false)or 8 -RadioEntry.FromAI=FromAI -RadioEntry.IsGroup=IsGroup -if Tactical then -self.TacticalQueue:Push(RadioEntry) -elseif IsPrio then -self.PrioRadioQueue:Push(RadioEntry) -else -self.RadioQueue:Push(RadioEntry) -end -return self -end -function AWACS:SetBullsEyeAlias(Name) -self:T(self.lid.."_SetBullsEyeAlias") -self.AOName=Name or"Rock" -return self -end -function AWACS:SetTOS(AICHours,CapHours) -self:T(self.lid.."SetTOS") -self.AwacsTimeOnStation=AICHours or 4 -self.CAPTimeOnStation=CapHours or 4 -return self -end -function AWACS:SetReassignmentPause(Seconds) -self.ReassignmentPause=Seconds or 180 -return self -end -function AWACS:SuppressScreenMessages(Switch) -self:T(self.lid.."_SetBullsEyeAlias") -self.SuppressScreenOutput=Switch or false -return self -end -function AWACS:ZipLip() -self:T(self.lid.."ZipLip") -self:SuppressScreenMessages(true) -self.PlayerGuidance=false -self.callsignshort=true -self.NoMissileCalls=true -return self -end -function AWACS:SetCustomCallsigns(translationTable) -self.callsignTranslations=translationTable -end -function AWACS:_GetGIDFromGroupOrName(Group) -self:T(self.lid.."_GetGIDFromGroupOrName") -self:T({Group}) -local GID=0 -local Outcome=false -local CallSign="Ghost 1" -local nametocheck=CallSign -if Group and type(Group)=="string"then -nametocheck=Group -elseif Group and Group:IsInstanceOf("GROUP")then -nametocheck=Group:GetName() -else -return false,0,CallSign -end -local managedgrps=self.ManagedGrps or{} -for _,_managed in pairs(managedgrps)do -local managed=_managed -if managed.GroupName==nametocheck then -GID=managed.GID -Outcome=true -CallSign=managed.CallSign -end -end -self:T({Outcome,GID,CallSign}) -return Outcome,GID,CallSign -end -function AWACS:_EventHandler(EventData) -self:T(self.lid.."_EventHandler") -self:T({Event=EventData.id}) -local Event=EventData -if Event.id==EVENTS.PlayerEnterAircraft or Event.id==EVENTS.PlayerEnterUnit then -if Event.IniCoalition==self.coalition then -self:_SetClientMenus() -end -end -if Event.id==EVENTS.PlayerLeaveUnit then -self:T("Player group left unit: "..Event.IniGroupName) -self:T("Player name left: "..Event.IniPlayerName) -self:T("Coalition = "..UTILS.GetCoalitionName(Event.IniCoalition)) -if Event.IniCoalition==self.coalition then -local Outcome,GID,CallSign=self:_GetGIDFromGroupOrName(Event.IniGroupName) -if Outcome and GID>0 then -self:T("Task Abort and Checkout Called") -self:_TaskAbort(Event.IniGroupName) -self:_CheckOut(nil,GID,true) -end -end -end -if Event.id==EVENTS.Ejection or Event.id==EVENTS.Crash or Event.id==EVENTS.Dead or Event.id==EVENTS.PilotDead then -if Event.IniCoalition==self.coalition then -local Outcome,GID,CallSign=self:_GetGIDFromGroupOrName(Event.IniGroupName) -if Outcome and GID>0 then -self:_TaskAbort(Event.IniGroupName) -self:_CheckOut(nil,GID,true) -end -end -end -if Event.id==EVENTS.Shot and self.PlayerGuidance and not self.NoMissileCalls then -if Event.IniCoalition~=self.coalition then -self:T("Shot from: "..Event.IniGroupName) -local position=Event.IniGroup:GetCoordinate() -if not position then return self end -local Category=Event.WeaponCategory -local WeaponDesc=EventData.Weapon:getDesc() -self:T({WeaponDesc}) -if WeaponDesc.category==1 and(WeaponDesc.missileCategory==1 or WeaponDesc.missileCategory==2)then -self:T("AAM or SAM Missile fired") -local warndist=25 -local Type="SAM" -if WeaponDesc.category==1 then -Type="Missile" -local guidance=WeaponDesc.guidance or 4 -if guidance==2 then -warndist=10 -elseif guidance==3 then -warndist=25 -elseif guidance==4 then -warndist=15 -elseif guidance==5 then -warndist=10 -end -end -self:_MissileWarning(position,Type,warndist) -end -end -end -return self -end -function AWACS:_MissileWarning(Coordinate,Type,Warndist) -self:T(self.lid.."_MissileWarning Type="..Type.." WarnDist="..Warndist) -if not Coordinate then return self end -local shotzone=ZONE_RADIUS:New("WarningZone",Coordinate:GetVec2(),UTILS.NMToMeters(Warndist)) -local targetgrpset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryAirplane():FilterActive():FilterZones({shotzone}):FilterOnce() -if targetgrpset:Count()>0 then -local targets=targetgrpset:GetSetObjects() -for _,_grp in pairs(targets)do -if _grp and _grp:IsAlive()then -local isPlayer=_grp:IsPlayer() -if isPlayer then -local callsign=self:_GetCallSign(_grp) -local defend=self.gettext:GetEntry("DEFEND",self.locale) -local text=string.format(defend,callsign,Type,Type,Type) -self:_NewRadioEntry(text,text,0,false,self.debug,true,false,true) -end -end -end -end -return self -end -function AWACS:SetRadarBlur(Percent) -local percent=Percent or 15 -if percent<0 then percent=0 end -if percent>100 then percent=100 end -self.RadarBlur=Percent -return self -end -function AWACS:SetColdWar() -self.ModernEra=false -self.AwacsROT=AWACS.ROT.PASSIVE -self.AwacsROE=AWACS.ROE.VID -self.RadarBlur=25 -self:SetInterceptTimeline(35,25,15) -return self -end -function AWACS:SetModernEra() -self.ModernEra=true -self.AwacsROT=AWACS.ROT.EVADE -self.AwacsROE=AWACS.ROE.BVR -self.RadarBlur=15 -return self -end -function AWACS:SetModernEraDefensive() -self.ModernEra=true -self.AwacsROT=AWACS.ROT.EVADE -self.AwacsROE=AWACS.ROE.IFF -self.RadarBlur=15 -return self -end -function AWACS:SetModernEraAggressive() -self.ModernEra=true -self.AwacsROT=AWACS.ROT.RETURNFIRE -self.AwacsROE=AWACS.ROE.BVR -self.RadarBlur=15 -return self -end -function AWACS:SetPolicingModern() -self.ModernEra=true -self.AwacsROT=AWACS.ROT.BYPASSESCAPE -self.AwacsROE=AWACS.ROE.VID -self.RadarBlur=15 -return self -end -function AWACS:SetPolicingColdWar() -self.ModernEra=false -self.AwacsROT=AWACS.ROT.BYPASSESCAPE -self.AwacsROE=AWACS.ROE.VID -self.RadarBlur=25 -self:SetInterceptTimeline(35,25,15) -return self -end -function AWACS:SetPlayerGuidance(Switch) -if(Switch==nil)or(Switch==true)then -self.PlayerGuidance=true -else -self.PlayerGuidance=false -end -return self -end -function AWACS:GetName() -return self.Name or"not set" -end -function AWACS:SetInterceptTimeline(TacDistance,MeldDistance,ThreatDistance) -self.TacDistance=TacDistance or 45 -self.MeldDistance=MeldDistance or 35 -self.ThreatDistance=ThreatDistance or 25 -return self -end -function AWACS:SetAdditionalZone(Zone,Draw) -self:T(self.lid.."SetAdditionalZone") -self.BorderZone=Zone -if self.debug then -Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) -MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) -elseif Draw then -Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) -end -return self -end -function AWACS:SetRejectionZone(Zone,Draw) -self:T(self.lid.."SetRejectionZone") -self.RejectZone=Zone -if Draw then -Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) -elseif self.debug then -Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) -MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) -end -return self -end -function AWACS:DrawFEZ() -self.OpsZone:DrawZone(self.coalition,{1,0,0},1,{1,0,0},0.2,5,true) -return self -end -function AWACS:SetAwacsDetails(CallSign,CallSignNo,Angels,Speed,Heading,Leg) -self:T(self.lid.."SetAwacsDetails") -self.CallSign=CallSign or CALLSIGN.AWACS.Magic -self.CallSignNo=CallSignNo or 1 -self.AwacsAngels=Angels or 25 -local speed=Speed or 250 -self.SpeedBase=speed -self.Speed=speed -self.Heading=Heading or 0 -self.Leg=Leg or 25 -return self -end -function AWACS:SetCustomAWACSCallSign(CallsignTable) -self:T(self.lid.."SetCustomAWACSCallSign") -self.CallSignClear=CallsignTable -return self -end -function AWACS:AddGroupToDetection(Group) -self:T(self.lid.."AddGroupToDetection") -if Group and Group.ClassName and Group.ClassName=="GROUP"then -self.DetectionSet:AddGroup(Group) -elseif Group and Group.ClassName and Group.ClassName=="SET_GROUP"then -self.DetectionSet:AddSet(Group) -end -return self -end -function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey) -self:T(self.lid.."SetSRS") -self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone" -self.Gender=Gender or MSRS.gender or"male" -self.Culture=Culture or MSRS.culture or"en-US" -self.Port=Port or MSRS.port or 5002 -self.Voice=Voice or MSRS.voice -self.PathToGoogleKey=PathToGoogleKey -self.AccessKey=AccessKey -self.Volume=Volume or 1.0 -self.AwacsSRS=MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation) -self.AwacsSRS:SetCoalition(self.coalition) -self.AwacsSRS:SetGender(self.Gender) -self.AwacsSRS:SetCulture(self.Culture) -self.AwacsSRS:SetPort(self.Port) -self.AwacsSRS:SetLabel("AWACS") -self.AwacsSRS:SetVolume(Volume) -if self.PathToGoogleKey then -self.AwacsSRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey) -self.AwacsSRS:SetProvider(MSRS.Provider.GOOGLE) -end -if(not PathToGoogleKey)and self.AwacsSRS:GetProvider()==MSRS.Provider.GOOGLE then -self.PathToGoogleKey=MSRS.poptions.gcloud.credentials -self.Voice=Voice or MSRS.poptions.gcloud.voice -self.AccessKey=AccessKey or MSRS.poptions.gcloud.key -end -self.AwacsSRS:SetVoice(self.Voice) -return self -end -function AWACS:SetSRSVoiceCAP(Gender,Culture,Voice) -self:T(self.lid.."SetSRSVoiceCAP") -self.CAPGender=Gender or"male" -self.CAPCulture=Culture or"en-US" -self.CAPVoice=Voice or"en-GB-Standard-B" -return self -end -function AWACS:SetAICAPDetails(Callsign,MaxAICap,TOS,Speed) -self:T(self.lid.."SetAICAPDetails") -self.CapSpeedBase=Speed or 270 -self.CAPTimeOnStation=TOS or 4 -self.MaxAIonCAP=MaxAICap or 4 -self.AICAPCAllName=Callsign or CALLSIGN.Aircraft.Colt -return self -end -function AWACS:SetEscort(EscortNumber) -self:T(self.lid.."SetEscort") -if EscortNumber and EscortNumber>0 then -self.HasEscorts=true -self.EscortNumber=EscortNumber -else -self.HasEscorts=false -self.EscortNumber=0 -end -return self -end -function AWACS:_MessageVector(GID,Tag,Coordinate,Angels) -self:T(self.lid.."_MessageVector") -local managedgroup=self.ManagedGrps[GID] -local Tag=Tag or"" -if managedgroup and Coordinate then -local tocallsign=managedgroup.CallSign or"Ghost 1" -local group=managedgroup.Group -local groupposition=group:GetCoordinate() -local BRtext,BRtextTTS=self:_ToStringBR(groupposition,Coordinate) -local vector=self.gettext:GetEntry("VECTORTO",self.locale) -local vectortts=self.gettext:GetEntry("VECTORTOTTS",self.locale) -local angelstxt=self.gettext:GetEntry("ANGELS",self.locale) -local text=string.format(vectortts,tocallsign,self.callsigntxt,Tag,BRtextTTS) -local textScreen=string.format(vector,tocallsign,self.callsigntxt,Tag,BRtext) -if Angels then -text=text..angelstxt..tostring(Angels).."." -textScreen=textScreen..angelstxt..tostring(Angels).."." -end -self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false) -end -return self -end -function AWACS:_StartEscorts(Shiftchange) -self:T(self.lid.."_StartEscorts") -local AwacsFG=self.AwacsFG -local group=AwacsFG:GetGroup() -local timeonstation=(self.EscortsTimeOnStation+self.ShiftChangeTime)*3600 -for i=1,self.EscortNumber do -local escort=AUFTRAG:NewESCORT(group,{x=-100*((i+(i%2))/2),y=0,z=(100+100*((i+(i%2))/2))*(-1)^i},45,{"Air"}) -escort:SetRequiredAssets(1) -escort:SetTime(nil,timeonstation) -self.AirWing:AddMission(escort) -self.CatchAllMissions[#self.CatchAllMissions+1]=escort -if Shiftchange then -self.EscortMissionReplacement[i]=escort -else -self.EscortMission[i]=escort -end -end -return self -end -function AWACS:_StartSettings(FlightGroup,Mission) -self:T(self.lid.."_StartSettings") -local Mission=Mission -local AwacsFG=FlightGroup -if self.AwacsMission:GetName()==Mission:GetName()then -self:T("Setting up Awacs") -AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false) -AwacsFG:SwitchRadio(self.Frequency,self.Modulation) -AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000) -AwacsFG:SetHomebase(self.Airbase) -AwacsFG:SetDefaultCallsign(self.CallSign,self.CallSignNo) -AwacsFG:SetDefaultROE(ENUMS.ROE.WeaponHold) -AwacsFG:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN) -AwacsFG:SetDefaultEPLRS(self.ModernEra) -AwacsFG:SetDespawnAfterLanding() -AwacsFG:SetFuelLowRTB(true) -AwacsFG:SetFuelLowThreshold(20) -local group=AwacsFG:GetGroup() -group:SetCommandInvisible(self.invisible) -group:SetCommandImmortal(self.immortal) -group:CommandSetCallsign(self.CallSign,self.CallSignNo,2) -group:CommandEPLRS(self.ModernEra,5) -self.AwacsFG=AwacsFG -self.callsigntxt=string.format("%s",self.CallSignClear[self.CallSign]) -self:__CheckRadioQueue(10) -if self.HasEscorts then -self:_StartEscorts() -end -self.AwacsTimeStamp=timer.getTime() -self.EscortsTimeStamp=timer.getTime() -self.PictureTimeStamp=timer.getTime()+10*60 -self.AwacsReady=true -self:Started() -elseif self.ShiftChangeAwacsRequested and self.AwacsMissionReplacement and self.AwacsMissionReplacement:GetName()==Mission:GetName()then -self:T("Setting up Awacs Replacement") -AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false) -AwacsFG:SwitchRadio(self.Frequency,self.Modulation) -AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000) -AwacsFG:SetHomebase(self.Airbase) -self.CallSignNo=self.CallSignNo+1 -AwacsFG:SetDefaultCallsign(self.CallSign,self.CallSignNo) -AwacsFG:SetDefaultROE(ENUMS.ROE.WeaponHold) -AwacsFG:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN) -AwacsFG:SetDefaultEPLRS(self.ModernEra) -AwacsFG:SetDespawnAfterLanding() -AwacsFG:SetFuelLowRTB(true) -AwacsFG:SetFuelLowThreshold(20) -local group=AwacsFG:GetGroup() -group:SetCommandInvisible(self.invisible) -group:SetCommandImmortal(self.immortal) -group:CommandSetCallsign(self.CallSign,self.CallSignNo,2) -self.callsigntxt=string.format("%s",self.CallSignClear[self.CallSign]) -local shifting=self.gettext:GetEntry("SHIFTCHANGE",self.locale) -local text=string.format(shifting,self.callsigntxt,self.AOName or"Rock") -self:T(self.lid..text) -AwacsFG:RadioTransmission(text,1,false) -self.AwacsFG=AwacsFG -if self.HasEscorts then -self:_StartEscorts(true) -end -self.AwacsTimeStamp=timer.getTime() -self.EscortsTimeStamp=timer.getTime() -self.AwacsReady=true -end -return self -end -function AWACS:_ToStringBULLS(Coordinate,ssml,TTS) -self:T(self.lid.."_ToStringBULLS") -local bullseyename=self.AOName or"Rock" -local BullsCoordinate=self.AOCoordinate -local DirectionVec3=BullsCoordinate:GetDirectionVec3(Coordinate) -local AngleRadians=Coordinate:GetAngleRadians(DirectionVec3) -local Distance=Coordinate:Get2DDistance(BullsCoordinate) -local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),0) -local Bearing=string.format('%03d',AngleDegrees) -local Distance=UTILS.Round(UTILS.MetersToNM(Distance),0) -if ssml then -return string.format("%s %03d, %d",bullseyename,Bearing,Distance) -end -if TTS then -Bearing=self:_ToStringBullsTTS(Bearing) -local zero=self.gettext:GetEntry("ZERO",self.locale) -local BearingTTS=string.gsub(Bearing,"0",zero) -return string.format("%s %s, %d",bullseyename,BearingTTS,Distance) -else -return string.format("%s %s, %d",bullseyename,Bearing,Distance) -end -end -function AWACS:_ToStringBullsTTS(Text) -local text=Text -text=string.gsub(text,"Bullseye","Bulls eye") -text=string.gsub(text,"%d","%1 ") -text=string.gsub(text," ,",".") -text=string.gsub(text," $","") -return text -end -function AWACS:_GetManagedGrpID(Group) -if not Group or not Group:IsAlive()then -self:T(self.lid.."_GetManagedGrpID - Requested Group is not alive!") -return 0,false,"" -end -self:T(self.lid.."_GetManagedGrpID for "..Group:GetName()) -local GID=0 -local Outcome=false -local CallSign="Ghost 1" -local nametocheck=Group:GetName() -local managedgrps=self.ManagedGrps or{} -for _,_managed in pairs(managedgrps)do -local managed=_managed -if managed.GroupName==nametocheck then -GID=managed.GID -Outcome=true -CallSign=managed.CallSign -end -end -return GID,Outcome,CallSign -end -function AWACS:_GetCallSign(Group,GID,IsPlayer) -self:T(self.lid.."_GetCallSign - GID "..tostring(GID)) -if GID and type(GID)=="number"and GID>0 then -local managedgroup=self.ManagedGrps[GID] -self:T("Saved Callsign for TTS = "..tostring(managedgroup.CallSign)) -return managedgroup.CallSign -end -local callsign="Ghost 1" -if Group and Group:IsAlive()then -callsign=Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations) -end -return callsign -end -function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) -if not ShortCallsign or ShortCallsign==false then -self.callsignshort=false -else -self.callsignshort=true -end -self.keepnumber=Keepnumber or false -self.callsignTranslations=CallsignTranslations -return self -end -function AWACS:_UpdateContactFromCluster(CID) -self:T(self.lid.."_UpdateContactFromCluster CID="..CID) -local existingcontact=self.Contacts:PullByID(CID) -local ContactTable=existingcontact.Cluster.Contacts or{} -local function GetFirstAliveContact(table) -for _,_contact in pairs(table)do -local contact=_contact -if contact and contact.group and contact.group:IsAlive()then -return contact -end -end -return nil -end -local NewContact=GetFirstAliveContact(ContactTable) -if NewContact then -existingcontact.Contact=NewContact -self.Contacts:Push(existingcontact,existingcontact.CID) -end -return self -end -function AWACS:_CheckMerges() -self:T(self.lid.."_CheckMerges") -for _id,_pilot in pairs(self.ManagedGrps)do -local pilot=_pilot -if pilot.Group and pilot.Group:IsAlive()then -local ppos=pilot.Group:GetCoordinate() -local pcallsign=pilot.CallSign -self:T(self.lid.."Checking for "..pcallsign) -if ppos then -self.Contacts:ForEach( -function(Contact) -local contact=Contact -local cpos=contact.Cluster.coordinate or contact.Contact.position or contact.Contact.group:GetCoordinate() -local dist=ppos:Get2DDistance(cpos) -local distnm=UTILS.Round(UTILS.MetersToNM(dist),0) -if(pilot.IsPlayer or self.debug)and distnm<=5 and not contact.MergeCallDone then -local label=contact.EngagementTag or"" -if not contact.MergeCallDone or not string.find(label,pcallsign)then -self:T(self.lid.."Merged") -self:_MergedCall(_id) -contact.MergeCallDone=true -end -end -end -) -end -end -end -return self -end -function AWACS:_CleanUpContacts() -self:T(self.lid.."_CleanUpContacts") -if self.Contacts:Count()>0 then -local deadcontacts=FIFO:New() -self.Contacts:ForEach( -function(Contact) -local contact=Contact -if not contact.Contact.group:IsAlive()or contact.Target:IsDead()or contact.Target:IsDestroyed()or contact.Target:CountTargets()==0 then -deadcontacts:Push(contact,contact.CID) -self:T("DEAD contact CID="..contact.CID) -end -end -) -if deadcontacts:Count()>0 and(not self.NoGroupTags)then -self:T("DEAD count="..deadcontacts:Count()) -deadcontacts:ForEach( -function(Contact) -local contact=Contact -local vanished=self.gettext:GetEntry("VANISHED",self.locale) -local vanishedtts=self.gettext:GetEntry("VANISHEDTTS",self.locale) -local text=string.format(vanishedtts,self.callsigntxt,contact.TargetGroupNaming) -local textScreen=string.format(vanished,self.callsigntxt,contact.TargetGroupNaming) -self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true) -self.Contacts:PullByID(contact.CID) -end -) -end -if self.Contacts:Count()>0 then -self.Contacts:ForEach( -function(Contact) -local contact=Contact -self:_UpdateContactFromCluster(contact.CID) -end -) -end -deadcontacts:Clear() -end -return self -end -function AWACS:_GetIdlePilots() -self:T(self.lid.."_GetIdlePilots") -local AIPilots={} -local HumanPilots={} -for _name,_entry in pairs(self.ManagedGrps)do -local entry=_entry -self:T("Looking at entry "..entry.GID.." Name "..entry.GroupName) -local managedtask=self:_ReadAssignedTaskFromGID(entry.GID) -local overridetask=false -if managedtask then -self:T("Current task = "..(managedtask.ToDo or"Unknown")) -if managedtask.ToDo==AWACS.TaskDescription.ANCHOR then -overridetask=true -end -end -if entry.IsAI then -if entry.FlightGroup:IsAirborne()and((not entry.HasAssignedTask)or overridetask)then -self:T("Adding AI with Callsign: "..entry.CallSign) -AIPilots[#AIPilots+1]=_entry -end -elseif entry.IsPlayer and(not entry.Blocked)and(not entry.Group:IsHelicopter())then -if(not entry.HasAssignedTask)or overridetask then -local TNow=timer.getTime() -if entry.LastTasking and(TNow-entry.LastTasking>self.ReassignTime)then -self:T("Adding Human with Callsign: "..entry.CallSign) -HumanPilots[#HumanPilots+1]=_entry -end -end -end -end -return AIPilots,HumanPilots -end -function AWACS:_TargetSelectionProcess(Untargeted) -self:T(self.lid.."_TargetSelectionProcess") -local maxtargets=3 -local contactstable=self.Contacts:GetDataTable() -local targettable=FIFO:New() -local sortedtargets=FIFO:New() -local prefiltered=FIFO:New() -local HaveTargets=false -self:T(self.lid.."Initial count: "..self.Contacts:Count()) -if Untargeted then -self.Contacts:ForEach( -function(Contact) -local contact=Contact -if contact.Contact.group:IsAlive()and(contact.Status==AWACS.TaskStatus.IDLE or contact.Status==AWACS.TaskStatus.UNASSIGNED)then -if self.AwacsROE==AWACS.ROE.POLICE or self.AwacsROE==AWACS.ROE.VID then -if not(contact.IFF==AWACS.IFF.FRIENDLY or contact.IFF==AWACS.IFF.NEUTRAL)then -prefiltered:Push(contact,contact.CID) -end -else -prefiltered:Push(contact,contact.CID) -end -end -end -) -contactstable=prefiltered:GetDataTable() -self:T(self.lid.."Untargeted: "..prefiltered:Count()) -end -for _,_contact in pairs(contactstable)do -local contact=_contact -local checked=false -local contactname=contact.TargetGroupNaming or"ZETA" -local typename=contact.ReportingName or"Unknown" -self:T(self.lid..string.format("Looking at group %s type %s",contactname,typename)) -local contactcoord=contact.Cluster.coordinate or contact.Contact.position or contact.Contact.group:GetCoordinate() -local contactvec2=contactcoord:GetVec2() -if self.RejectZone then -local isinrejzone=self.RejectZone:IsVec2InZone(contactvec2) -if isinrejzone then -self:T(self.lid.."Across Border = YES - ignore") -checked=true -end -end -if not self.GCI then -local HVTCoordinate=self.OrbitZone:GetCoordinate() -local distance=UTILS.NMToMeters(200) -if contactcoord then -distance=HVTCoordinate:Get2DDistance(contactcoord) -end -self:T(self.lid.."HVT Distance = "..UTILS.Round(UTILS.MetersToNM(distance),0)) -if UTILS.MetersToNM(distance)<=45 and not checked then -self:T(self.lid.."In HVT Distance = YES") -targettable:Push(contact,distance) -checked=true -end -end -local isinopszone=self.OpsZone:IsVec2InZone(contactvec2) -local distance=self.OpsZone:Get2DDistance(contactcoord) -if isinopszone and not checked then -self:T(self.lid.."In FEZ = YES") -targettable:Push(contact,distance) -checked=true -end -local isinopszone=self.ControlZone:IsVec2InZone(contactvec2) -if isinopszone and not checked then -self:T(self.lid.."In Radar Zone = YES") -local distance=self.AOCoordinate:Get2DDistance(contactcoord) -local AOdist=UTILS.Round(UTILS.MetersToNM(distance),0) -if not contactcoord.Heading then -contactcoord.Heading=self.intel:CalcClusterDirection(contact.Cluster) -end -local aspect=contactcoord:ToStringAspect(self.ControlZone:GetCoordinate()) -local sizing=contact.Cluster.size or self.intel:ClusterCountUnits(contact.Cluster)or 1 -sizing=math.fmod((sizing*0.1),1) -local AOdist2=(AOdist/2)*sizing -AOdist2=UTILS.Round((AOdist/2)+((AOdist/2)-AOdist2),0) -self:T(self.lid.."Aspect = "..aspect.." | Size = "..sizing) -if(AOdist2<75)or(aspect=="Hot")then -local text=string.format("In AO(Adj) dist = %d(%d) NM",AOdist,AOdist2) -self:T(self.lid..text) -targettable:Push(contact,distance) -checked=true -end -end -if self.BorderZone then -local isinborderzone=self.BorderZone:IsVec2InZone(contactvec2) -if isinborderzone and not checked then -self:T(self.lid.."In BorderZone = YES") -targettable:Push(contact,distance) -checked=true -end -end -end -self:T(self.lid.."Post filter count: "..targettable:Count()) -if targettable:Count()>maxtargets then -local targets=targettable:GetSortedDataTable() -targettable:Clear() -for i=1,maxtargets do -targettable:Push(targets[i]) -end -end -sortedtargets:Clear() -prefiltered:Clear() -if targettable:Count()>0 then -HaveTargets=true -end -return HaveTargets,targettable -end -function AWACS:_CreatePicture(AO,Callsign,GID,MaxEntries,IsGeneral) -self:T(self.lid.."_CreatePicture AO="..tostring(AO).." for "..Callsign.." GID "..GID) -local managedgroup=nil -local group=nil -local groupcoord=nil -if not IsGeneral then -managedgroup=self.ManagedGrps[GID] -group=managedgroup.Group -groupcoord=group:GetCoordinate() -end -local fifo=self.PictureAO -local maxentries=self.maxspeakentries or 3 -if MaxEntries and MaxEntries>0 and MaxEntries<=3 then -maxentries=MaxEntries -end -local counter=0 -if not AO then -end -local entries=fifo:GetSize() -if entries1 then -text=text.." "..threatsizetext.."." -textScreen=textScreen.." "..threatsizetext.."." -end -if contact.EngagementTag then -text=text.." "..contact.EngagementTag -textScreen=textScreen.." "..contact.EngagementTag -end -local RadioEntry_IsGroup=false -local RadioEntry_ToScreen=self.debug -if managedgroup and not IsGeneral then -RadioEntry_IsGroup=managedgroup.IsPlayer -RadioEntry_ToScreen=managedgroup.IsPlayer -end -self:_NewRadioEntry(text,textScreen,GID,RadioEntry_IsGroup,RadioEntry_ToScreen,true,false) -end -end -fifo:Clear() -return self -end -function AWACS:_CreateBogeyDope(Callsign,GID,Tactical) -self:T(self.lid.."_CreateBogeyDope for "..Callsign.." GID "..GID) -local managedgroup=self.ManagedGrps[GID] -local group=managedgroup.Group -local groupcoord=group:GetCoordinate() -local fifo=self.ContactsAO -local maxentries=1 -local counter=0 -local entries=fifo:GetSize() -if entries0 then -local IDstack=self.PictureEWR:GetSortedDataTable() -local weneed=3-clustersAO -self:T(string.format("Picture - adding %d/%d contacts from EWR",weneed,clustersEWR)) -if weneed>clustersEWR then -weneed=clustersEWR -end -for i=1,weneed do -self.PictureAO:Push(IDstack[i]) -end -end -clustersAO=self.PictureAO:GetSize() -if clustersAO==0 and clustersEWR==0 then -local picclean=self.gettext:GetEntry("PICCLEAN",self.locale) -text=string.format(picclean,gcallsign,self.callsigntxt) -textScreen=text -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) -else -if clustersAO>0 then -local picture=self.gettext:GetEntry("PICTURE",self.locale) -text=string.format("%s, %s. %s. ",gcallsign,self.callsigntxt,picture) -textScreen=string.format("%s, %s. %s. ",gcallsign,self.callsigntxt,picture) -local onetxt=self.gettext:GetEntry("ONE",self.locale) -local grptxt=self.gettext:GetEntry("GROUP",self.locale) -local groupstxt=self.gettext:GetEntry("GROUPMULTI",self.locale) -if clustersAO==1 then -text=string.format("%s%s %s. ",text,onetxt,grptxt) -textScreen=string.format("%s%s %s.\n",textScreen,onetxt,grptxt) -else -text=string.format("%s%d %s. ",text,clustersAO,groupstxt) -textScreen=string.format("%s%d %s.\n",textScreen,clustersAO,groupstxt) -end -self:_NewRadioEntry(text,textScreen,GID,Outcome,true,true,false) -self:_CreatePicture(true,gcallsign,GID,3,general) -self.PictureAO:Clear() -self.PictureEWR:Clear() -end -end -elseif self.AwacsFG then -local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) -text=string.format(nocheckin,gcallsign,self.callsigntxt) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) -end -return self -end -function AWACS:_BogeyDope(Group,Tactical) -self:T(self.lid.."_BogeyDope") -local text="" -local textScreen="" -local GID,Outcome=self:_GetManagedGrpID(Group) -local gcallsign=self:_GetCallSign(Group,GID)or"Ghost 1" -if not self.intel then -local clean=self.gettext:GetEntry("CLEAN",self.locale) -text=string.format(clean,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:_NewRadioEntry(text,text,0,false,true,true,false,true,Tactical) -return self -end -if Outcome then -local managedgroup=self.ManagedGrps[GID] -local pilotgroup=managedgroup.Group -local pilotcoord=managedgroup.Group:GetCoordinate() -local contactstable=self.Contacts:GetDataTable() -for _,_contact in pairs(contactstable)do -local managedcontact=_contact -local contactposition=managedcontact.Cluster.coordinate or managedcontact.Contact.position -local coordVec2=contactposition:GetVec2() -local dist=pilotcoord:Get2DDistance(contactposition) -if self.ControlZone:IsVec2InZone(coordVec2)then -self.ContactsAO:Push(managedcontact,dist) -elseif self.BorderZone and self.BorderZone:IsVec2InZone(coordVec2)then -self.ContactsAO:Push(managedcontact,dist) -else -if self.OrbitZone then -local distance=contactposition:Get2DDistance(self.OrbitZone:GetCoordinate()) -if(distance<=UTILS.NMToMeters(45))then -self.ContactsAO:Push(managedcontact,distance) -end -end -end -end -local contactsAO=self.ContactsAO:GetSize() -if contactsAO==0 then -local clean=self.gettext:GetEntry("CLEAN",self.locale) -text=string.format(clean,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:_NewRadioEntry(text,textScreen,GID,Outcome,Outcome,true,false,true,Tactical) -else -if contactsAO>0 then -local dope=self.gettext:GetEntry("DOPE",self.locale) -text=string.format(dope,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -textScreen=string.format(dope,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -local onetxt=self.gettext:GetEntry("ONE",self.locale) -local grptxt=self.gettext:GetEntry("GROUP",self.locale) -local groupstxt=self.gettext:GetEntry("GROUPMULTI",self.locale) -if contactsAO==1 then -text=string.format("%s%s %s. ",text,onetxt,grptxt) -textScreen=string.format("%s%s %s.\n",textScreen,onetxt,grptxt) -else -text=string.format("%s%d %s. ",text,contactsAO,groupstxt) -textScreen=string.format("%s%d %s.\n",textScreen,contactsAO,groupstxt) -end -self:_NewRadioEntry(text,textScreen,GID,Outcome,true,true,false,true,Tactical) -self:_CreateBogeyDope(self:_GetCallSign(Group,GID)or"Ghost 1",GID,Tactical) -end -end -elseif self.AwacsFG then -local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,Tactical) -end -return self -end -function AWACS:_ShowAwacsInfo(Group) -self:T(self.lid.."_ShowAwacsInfo") -local report=REPORT:New("Info") -report:Add("====================") -report:Add(string.format("AWACS %s",self.callsigntxt)) -report:Add(string.format("Radio: %.3f %s",self.Frequency,UTILS.GetModulationName(self.Modulation))) -report:Add(string.format("Bulls Alias: %s",self.AOName)) -report:Add(string.format("Coordinate: %s",self.AOCoordinate:ToStringLLDDM())) -report:Add("====================") -report:Add(string.format("Assignment Distance: %d NM",self.maxassigndistance)) -report:Add(string.format("TAC Distance: %d NM",self.TacDistance)) -report:Add(string.format("MELD Distance: %d NM",self.MeldDistance)) -report:Add(string.format("THREAT Distance: %d NM",self.ThreatDistance)) -report:Add("====================") -report:Add(string.format("ROE/ROT: %s, %s",self.AwacsROE,self.AwacsROT)) -MESSAGE:New(report:Text(),45,"AWACS"):ToGroup(Group) -return self -end -function AWACS:_VID(Group,Declaration) -self:T(self.lid.."_VID") -local GID,Outcome,Callsign=self:_GetManagedGrpID(Group) -local text="" -local TextTTS="" -if Outcome then -local managedgroup=self.ManagedGrps[GID] -local group=managedgroup.Group -local position=group:GetCoordinate() -local radius=UTILS.NMToMeters(self.DeclareRadius)or UTILS.NMToMeters(5) -local TID=managedgroup.CurrentTask or 0 -if TID>0 then -local task=self.ManagedTasks:ReadByID(TID) -if task.ToDo~=AWACS.TaskDescription.VID then -return self -end -if task.Status~=AWACS.TaskStatus.ASSIGNED then -return self -end -local CID=task.Cluster.CID -local cluster=self.Contacts:ReadByID(CID) -if cluster then -local gposition=cluster.Contact.group:GetCoordinate() -local cposition=gposition or cluster.Cluster.coordinate or cluster.Contact.position -local distance=cposition:Get2DDistance(position) -distance=UTILS.Round(distance,0)+1 -if distance<=radius or self.debug then -self:T("Contact VID as "..Declaration) -cluster.IFF=Declaration -task.Status=AWACS.TaskStatus.SUCCESS -self.ManagedTasks:PullByID(TID) -self.ManagedTasks:Push(task,TID) -self.Contacts:PullByID(CID) -self.Contacts:Push(cluster,CID) -local vidpos=self.gettext:GetEntry("VIDPOS",self.locale) -text=string.format(vidpos,Callsign,self.callsigntxt,Declaration) -self:T(text) -else -self:T("Contact VID not close enough") -local vidneg=self.gettext:GetEntry("VIDNEG",self.locale) -text=string.format(vidneg,Callsign,self.callsigntxt) -self:T(text) -end -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) -end -end -elseif self.AwacsFG then -local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) -end -return self -end -function AWACS:_Declare(Group) -self:T(self.lid.."_Declare") -local GID,Outcome,Callsign=self:_GetManagedGrpID(Group) -local text="" -local TextTTS="" -if Outcome then -local managedgroup=self.ManagedGrps[GID] -local group=managedgroup.Group -local position=group:GetCoordinate() -local radius=UTILS.NMToMeters(self.DeclareRadius)or UTILS.NMToMeters(5) -local groupzone=ZONE_GROUP:New(group:GetName(),group,radius) -local Coalitions={"red","neutral"} -if self.coalition==coalition.side.NEUTRAL then -Coalitions={"red","blue"} -elseif self.coalition==coalition.side.RED then -Coalitions={"blue","neutral"} -end -local contactset=SET_GROUP:New():FilterCategoryAirplane():FilterCoalitions(Coalitions):FilterZones({groupzone}):FilterOnce() -local numbercontacts=contactset:CountAlive()or 0 -local foundcontacts={} -if numbercontacts>0 then -contactset:ForEach( -function(airpl) -local distance=position:Get2DDistance(airpl:GetCoordinate()) -distance=UTILS.Round(distance,0)+1 -foundcontacts[distance]=airpl -end -,{} -) -for _dist,_contact in UTILS.spairs(foundcontacts)do -local distanz=_dist -local contact=_contact -local ccoalition=contact:GetCoalition() -local ctypename=contact:GetTypeName() -local ffneutral=self.gettext:GetEntry("FFNEUTRAL",self.locale) -local fffriend=self.gettext:GetEntry("FFFRIEND",self.locale) -local ffhostile=self.gettext:GetEntry("FFHOSTILE",self.locale) -local ffspades=self.gettext:GetEntry("FFSPADES",self.locale) -local friendorfoe=ffneutral -if self.self.ModernEra then -if ccoalition==self.coalition then -friendorfoe=fffriend -elseif ccoalition==coalition.side.NEUTRAL then -friendorfoe=ffneutral -elseif ccoalition~=self.coalition then -friendorfoe=ffhostile -end -else -friendorfoe=ffspades -end -self:T(string.format("Distance %d ContactName %s Coalition %d (%s) TypeName %s",distanz,contact:GetName(),ccoalition,friendorfoe,ctypename)) -text=string.format("%s. %s. %s.",Callsign,self.callsigntxt,friendorfoe) -TextTTS=text -if self.ModernEra then -text=string.format("%s %s.",text,ctypename) -end -break -end -else -local ffclean=self.gettext:GetEntry("FFCLEAN",self.locale) -text=string.format("%s. %s. %s.",Callsign,self.callsigntxt,ffclean) -TextTTS=text -end -self:_NewRadioEntry(TextTTS,text,GID,Outcome,true,true,false,true) -elseif self.AwacsFG then -local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) -end -return self -end -function AWACS:_Commit(Group) -self:T(self.lid.."_Commit") -local GID,Outcome=self:_GetManagedGrpID(Group) -local text="" -if Outcome then -local Pilot=self.ManagedGrps[GID] -local currtaskid=Pilot.CurrentTask -local managedtask=self.ManagedTasks:ReadByID(currtaskid) -self:T(string.format("TID %d(%d) | ToDo %s | Status %s",currtaskid,managedtask.TID,managedtask.ToDo,managedtask.Status)) -if managedtask then -if managedtask.Status==AWACS.TaskStatus.REQUESTED then -managedtask=self.ManagedTasks:PullByID(currtaskid) -managedtask.Status=AWACS.TaskStatus.ASSIGNED -self.ManagedTasks:Push(managedtask,currtaskid) -self:T(string.format("COMMITTED - TID %d(%d) for GID %d | ToDo %s | Status %s",currtaskid,GID,managedtask.TID,managedtask.ToDo,managedtask.Status)) -Pilot.HasAssignedTask=true -Pilot.CurrentTask=currtaskid -self.ManagedGrps[GID]=Pilot -local copy=self.gettext:GetEntry("COPY",self.locale) -local targetedby=self.gettext:GetEntry("TARGETEDBY",self.locale) -text=string.format(copy,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -local EngagementTag=string.format(targetedby,Pilot.CallSign) -self:_UpdateContactEngagementTag(Pilot.ContactCID,EngagementTag,false,false,AWACS.TaskStatus.ASSIGNED) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) -else -self:E(self.lid.."Cannot find REQUESTED managed task with TID="..currtaskid.." for GID="..GID) -end -else -self:E(self.lid.."Cannot find managed task with TID="..currtaskid.." for GID="..GID) -end -elseif self.AwacsFG then -local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) -end -return self -end -function AWACS:_Judy(Group) -self:T(self.lid.."_Judy") -local GID,Outcome=self:_GetManagedGrpID(Group) -local text="" -if Outcome then -local Pilot=self.ManagedGrps[GID] -local currtaskid=Pilot.CurrentTask -local managedtask=self.ManagedTasks:ReadByID(currtaskid) -if managedtask then -if managedtask.Status==AWACS.TaskStatus.REQUESTED or managedtask.Status==AWACS.TaskStatus.UNASSIGNED then -managedtask=self.ManagedTasks:PullByID(currtaskid) -managedtask.Status=AWACS.TaskStatus.ASSIGNED -self.ManagedTasks:Push(managedtask,currtaskid) -local copy=self.gettext:GetEntry("COPY",self.locale) -local targetedby=self.gettext:GetEntry("TARGETEDBY",self.locale) -text=string.format(copy,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -local EngagementTag=string.format(targetedby,Pilot.CallSign) -self:_UpdateContactEngagementTag(Pilot.ContactCID,EngagementTag,false,false,AWACS.TaskStatus.ASSIGNED) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) -else -self:E(self.lid.."Cannot find REQUESTED or UNASSIGNED managed task with TID="..currtaskid.." for GID="..GID) -end -else -self:E(self.lid.."Cannot find managed task with TID="..currtaskid.." for GID="..GID) -end -elseif self.AwacsFG then -local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) -end -return self -end -function AWACS:_Unable(Group) -self:T(self.lid.."_Unable") -local GID,Outcome=self:_GetManagedGrpID(Group) -local text="" -if Outcome then -local Pilot=self.ManagedGrps[GID] -local currtaskid=Pilot.CurrentTask -local managedtask=self.ManagedTasks:ReadByID(currtaskid) -self:T(string.format("UNABLE for TID %d(%d) | ToDo %s | Status %s",currtaskid,managedtask.TID,managedtask.ToDo,managedtask.Status)) -if managedtask then -if managedtask.Status==AWACS.TaskStatus.REQUESTED then -managedtask=self.ManagedTasks:PullByID(currtaskid) -managedtask.IsUnassigned=true -managedtask.Status=AWACS.TaskStatus.FAILED -self.ManagedTasks:Push(managedtask,currtaskid) -self:T(string.format("REJECTED - TID %d(%d) for GID %d | ToDo %s | Status %s",currtaskid,GID,managedtask.TID,managedtask.ToDo,managedtask.Status)) -Pilot.HasAssignedTask=false -Pilot.CurrentTask=0 -Pilot.LastTasking=timer.getTime() -self.ManagedGrps[GID]=Pilot -local copy=self.gettext:GetEntry("COPY",self.locale) -text=string.format(copy,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -local EngagementTag="" -self:_UpdateContactEngagementTag(Pilot.ContactCID,EngagementTag,false,false,AWACS.TaskStatus.UNASSIGNED) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) -else -self:E(self.lid.."Cannot find REQUESTED managed task with TID="..currtaskid.." for GID="..GID) -end -else -self:E(self.lid.."Cannot find managed task with TID="..currtaskid.." for GID="..GID) -end -elseif self.AwacsFG then -local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) -end -return self -end -function AWACS:_TaskAbort(Group) -self:T(self.lid.."_TaskAbort") -local Outcome,GID=self:_GetGIDFromGroupOrName(Group) -local text="" -if Outcome then -local Pilot=self.ManagedGrps[GID] -self:T({Pilot}) -local currtaskid=Pilot.CurrentTask -local managedtask=self.ManagedTasks:ReadByID(currtaskid) -if managedtask then -self:T(string.format("ABORT for TID %d(%d) | ToDo %s | Status %s",currtaskid,managedtask.TID,managedtask.ToDo,managedtask.Status)) -if managedtask.Status==AWACS.TaskStatus.ASSIGNED then -managedtask=self.ManagedTasks:PullByID(currtaskid) -managedtask.Status=AWACS.TaskStatus.FAILED -managedtask.IsUnassigned=true -self.ManagedTasks:Push(managedtask,currtaskid) -self:T(string.format("ABORTED - TID %d(%d) for GID %d | ToDo %s | Status %s",currtaskid,GID,managedtask.TID,managedtask.ToDo,managedtask.Status)) -Pilot.HasAssignedTask=false -Pilot.CurrentTask=0 -Pilot.LastTasking=timer.getTime() -self.ManagedGrps[GID]=Pilot -local copy=self.gettext:GetEntry("COPY",self.locale) -text=string.format(copy,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -local EngagementTag="" -self:_UpdateContactEngagementTag(Pilot.ContactCID,EngagementTag,false,false,AWACS.TaskStatus.UNASSIGNED) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) -else -self:E(self.lid.."Cannot find ASSIGNED managed task with TID="..currtaskid.." for GID="..GID) -end -else -self:E(self.lid.."Cannot find managed task with TID="..currtaskid.." for GID="..GID) -end -elseif self.AwacsFG then -local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) -end -return self -end -function AWACS:_Showtask(Group) -self:T(self.lid.."_Showtask") -local GID,Outcome,Callsign=self:_GetManagedGrpID(Group) -local text="" -if Outcome then -local managedgroup=self.ManagedGrps[GID] -if managedgroup.IsPlayer then -if managedgroup.CurrentTask>0 and self.ManagedTasks:HasUniqueID(managedgroup.CurrentTask)then -local currenttask=self.ManagedTasks:ReadByID(managedgroup.CurrentTask) -if currenttask then -local status=currenttask.Status -local targettype=currenttask.Target:GetCategory() -local targetstatus=currenttask.Target:GetState() -local ToDo=currenttask.ToDo -local description=currenttask.ScreenText -local descTTS=currenttask.ScreenText -local callsign=Callsign -if self.debug then -local taskreport=REPORT:New("AWACS Tasking Display") -taskreport:Add("===============") -taskreport:Add(string.format("Task for Callsign: %s",Callsign)) -taskreport:Add(string.format("Task: %s with Status: %s",ToDo,status)) -taskreport:Add(string.format("Target of Type: %s",targettype)) -taskreport:Add(string.format("Target in State: %s",targetstatus)) -taskreport:Add("===============") -self:I(taskreport:Text()) -end -local pposition=managedgroup.Group:GetCoordinate()or managedgroup.LastKnownPosition -if currenttask.ToDo==AWACS.TaskDescription.INTERCEPT or currenttask.ToDo==AWACS.TaskDescription.VID then -local targetpos=currenttask.Target:GetCoordinate() -if pposition and targetpos then -local alti=currenttask.Cluster.altitude or currenttask.Contact.altitude or currenttask.Contact.group:GetAltitude() -local direction,direcTTS=self:_ToStringBRA(pposition,targetpos,alti) -description=description.."\nBRA "..direction -descTTS=descTTS..";BRA "..direcTTS -end -elseif currenttask.ToDo==AWACS.TaskDescription.ANCHOR or currenttask.ToDo==AWACS.TaskDescription.REANCHOR then -local targetpos=currenttask.Target:GetCoordinate() -local direction,direcTTS=self:_ToStringBR(pposition,targetpos) -description=description.."\nBR "..direction -descTTS=descTTS..";BR "..direcTTS -end -local statustxt=self.gettext:GetEntry("STATUS",self.locale) -local text=string.format("%s\n%s %s",description,statustxt,status) -local ttstext=string.format("%s. %s. %s",managedgroup.CallSign,self.callsigntxt,descTTS) -ttstext=string.gsub(ttstext,"\\n",";") -ttstext=string.gsub(ttstext,"VID","V I D") -self:_NewRadioEntry(ttstext,text,GID,true,true,false,false,true) -end -end -end -elseif self.AwacsFG then -local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) -end -return self -end -function AWACS:_CheckIn(Group) -self:T(self.lid.."_CheckIn "..Group:GetName()) -local GID,Outcome=self:_GetManagedGrpID(Group) -local text="" -local textTTS="" -if not Outcome then -self.ManagedGrpID=self.ManagedGrpID+1 -local managedgroup={} -managedgroup.Group=Group -managedgroup.GroupName=Group:GetName() -managedgroup.IsPlayer=true -managedgroup.IsAI=false -managedgroup.CallSign=self:_GetCallSign(Group,GID,true)or"Ghost 1" -managedgroup.CurrentAuftrag=0 -managedgroup.CurrentTask=0 -managedgroup.HasAssignedTask=true -managedgroup.Blocked=true -managedgroup.GID=self.ManagedGrpID -managedgroup.LastKnownPosition=Group:GetCoordinate() -managedgroup.LastTasking=timer.getTime() -GID=managedgroup.GID -self.ManagedGrps[self.ManagedGrpID]=managedgroup -local alphacheckbulls=self:_ToStringBULLS(Group:GetCoordinate()) -local alphacheckbullstts=self:_ToStringBULLS(Group:GetCoordinate(),false,true) -local alpha=self.gettext:GetEntry("ALPHACHECK",self.locale) -text=string.format("%s. %s. %s. %s",managedgroup.CallSign,self.callsigntxt,alpha,alphacheckbulls) -textTTS=string.format("%s. %s. %s. %s",managedgroup.CallSign,self.callsigntxt,alpha,alphacheckbullstts) -self:__CheckedIn(1,managedgroup.GID) -if self.PlayerStationName then -self:__AssignAnchor(5,managedgroup.GID,true,self.PlayerStationName) -else -self:__AssignAnchor(5,managedgroup.GID) -end -elseif self.AwacsFG then -local nocheckin=self.gettext:GetEntry("ALREADYCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -textTTS=text -end -self:_NewRadioEntry(textTTS,text,GID,Outcome,true,true,false) -return self -end -function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) -self:T(self.lid.."_CheckInAI "..Group:GetName().." to Auftrag Nr "..AuftragsNr) -local GID,Outcome=self:_GetManagedGrpID(Group) -local text="" -if not Outcome then -self.ManagedGrpID=self.ManagedGrpID+1 -local managedgroup={} -managedgroup.Group=Group -managedgroup.GroupName=Group:GetName() -managedgroup.FlightGroup=FlightGroup -managedgroup.IsPlayer=false -managedgroup.IsAI=true -local callsignstring=UTILS.GetCallsignName(self.AICAPCAllName) -if self.callsignTranslations and self.callsignTranslations[callsignstring]then -callsignstring=self.callsignTranslations[callsignstring] -end -local callsignmajor=math.fmod(self.AICAPCAllNumber,9) -local callsign=string.format("%s %d 1",callsignstring,callsignmajor) -if self.callsignshort then -callsign=string.format("%s %d",callsignstring,callsignmajor) -end -self:T("Assigned Callsign: "..callsign) -managedgroup.CallSign=callsign -managedgroup.CurrentAuftrag=AuftragsNr -managedgroup.HasAssignedTask=false -managedgroup.GID=self.ManagedGrpID -managedgroup.LastKnownPosition=Group:GetCoordinate() -self.ManagedGrps[self.ManagedGrpID]=managedgroup -FlightGroup:SetDefaultRadio(self.Frequency,self.Modulation,false) -FlightGroup:SwitchRadio(self.Frequency,self.Modulation) -local CAPVoice=self.CAPVoice -if self.PathToGoogleKey then -CAPVoice=self.CapVoices[math.floor(math.random(1,10))] -end -FlightGroup:SetSRS(self.PathToSRS,self.CAPGender,self.CAPCulture,CAPVoice,self.Port,self.PathToGoogleKey,"FLIGHT",1) -local checkai=self.gettext:GetEntry("CHECKINAI",self.locale) -text=string.format(checkai,self.callsigntxt,managedgroup.CallSign,self.CAPTimeOnStation,self.AOName) -self:_NewRadioEntry(text,text,managedgroup.GID,Outcome,false,true,true) -local alphacheckbulls=self:_ToStringBULLS(Group:GetCoordinate(),false,true) -local alpha=self.gettext:GetEntry("ALPHACHECK",self.locale) -text=string.format("%s. %s. %s. %s",managedgroup.CallSign,self.callsigntxt,alpha,alphacheckbulls) -self:__CheckedIn(1,managedgroup.GID) -local AW=FlightGroup.legion -if AW.HasOwnStation then -self:__AssignAnchor(5,managedgroup.GID,AW.HasOwnStation,AW.StationName) -else -self:__AssignAnchor(5,managedgroup.GID) -end -else -local nocheckin=self.gettext:GetEntry("ALREADYCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -end -self:_NewRadioEntry(text,text,GID,Outcome,false,true,false) -return self -end -function AWACS:_CheckOut(Group,GID,dead) -self:T(self.lid.."_CheckOut") -local GID,Outcome=self:_GetManagedGrpID(Group) -local text="" -if Outcome then -local safeflight=self.gettext:GetEntry("SAFEFLIGHT",self.locale) -text=string.format(safeflight,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -self:T(text) -local managedgroup=self.ManagedGrps[GID] -local Stack=managedgroup.AnchorStackNo -local Angels=managedgroup.AnchorStackAngels -local GroupName=managedgroup.GroupName -if managedgroup.IsPlayer then -if self.clientmenus:HasUniqueID(GroupName)then -local menus=self.clientmenus:PullByID(GroupName) -menus.basemenu:Remove() -if self.TacticalSubscribers[GroupName]then -local Freq=self.TacticalSubscribers[GroupName] -self.TacticalFrequencies[Freq]=Freq -self.TacticalSubscribers[GroupName]=nil -end -end -end -if managedgroup.CurrentTask and managedgroup.CurrentTask>0 then -self.ManagedTasks:PullByID(managedgroup.CurrentTask) -self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false) -end -self.ManagedGrps[GID]=nil -self:__CheckedOut(1,GID,Stack,Angels) -else -if not dead then -local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) -text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) -end -end -if not dead then -self:_NewRadioEntry(text,text,GID,Outcome,false,true,false) -end -return self -end -function AWACS:_SetClientMenus() -self:T(self.lid.."_SetClientMenus") -local clientset=self.clientset -local aliveset=clientset:GetSetObjects()or{} -local clientcount=0 -local clientcheckedin=0 -for _,_group in pairs(aliveset)do -local grp=_group -local cgrp=grp:GetGroup() -local cgrpname=nil -if cgrp and cgrp:IsAlive()then -cgrpname=cgrp:GetName() -self:T(cgrpname) -end -if self.MenuStrict then -if cgrp and cgrp:IsAlive()then -clientcount=clientcount+1 -local GID,checkedin=self:_GetManagedGrpID(cgrp) -if checkedin then -clientcheckedin=clientcheckedin+1 -local hasclientmenu=self.clientmenus:ReadByID(cgrpname) -local basemenu=hasclientmenu.basemenu -if hasclientmenu and(not hasclientmenu.menuset)then -self:T(self.lid.."Setting Menus for "..cgrpname) -basemenu:RemoveSubMenus() -local bogeydope=MENU_GROUP_COMMAND:New(cgrp,"Bogey Dope",basemenu,self._BogeyDope,self,cgrp) -local picture=MENU_GROUP_COMMAND:New(cgrp,"Picture",basemenu,self._Picture,self,cgrp) -local declare=MENU_GROUP_COMMAND:New(cgrp,"Declare",basemenu,self._Declare,self,cgrp) -local tasking=MENU_GROUP:New(cgrp,"Tasking",basemenu) -local showtask=MENU_GROUP_COMMAND:New(cgrp,"Showtask",tasking,self._Showtask,self,cgrp) -local commit -local unable -local abort -if self.PlayerCapAssignment then -commit=MENU_GROUP_COMMAND:New(cgrp,"Commit",tasking,self._Commit,self,cgrp) -unable=MENU_GROUP_COMMAND:New(cgrp,"Unable",tasking,self._Unable,self,cgrp) -abort=MENU_GROUP_COMMAND:New(cgrp,"Abort",tasking,self._TaskAbort,self,cgrp) -end -if self.AwacsROE==AWACS.ROE.POLICE or self.AwacsROE==AWACS.ROE.VID then -local vid=MENU_GROUP:New(cgrp,"VID as",tasking) -local hostile=MENU_GROUP_COMMAND:New(cgrp,"Hostile",vid,self._VID,self,cgrp,AWACS.IFF.ENEMY) -local neutral=MENU_GROUP_COMMAND:New(cgrp,"Neutral",vid,self._VID,self,cgrp,AWACS.IFF.NEUTRAL) -local friendly=MENU_GROUP_COMMAND:New(cgrp,"Friendly",vid,self._VID,self,cgrp,AWACS.IFF.FRIENDLY) -end -local tactical -if self.TacticalMenu then -tactical=MENU_GROUP:New(cgrp,"Tactical Radio",basemenu) -if self.TacticalSubscribers[cgrpname]then -local entry=MENU_GROUP_COMMAND:New(cgrp,"Unsubscribe",tactical,self._UnsubScribeTactRadio,self,cgrp) -else -for _,_freq in UTILS.spairs(self.TacticalFrequencies)do -local modu=UTILS.GetModulationName(self.TacticalModulation) -local text=string.format("Subscribe to %.3f %s",_freq,modu) -local entry=MENU_GROUP_COMMAND:New(cgrp,text,tactical,self._SubScribeTactRadio,self,cgrp,_freq) -end -end -end -local ainfo=MENU_GROUP_COMMAND:New(cgrp,"Awacs Info",basemenu,self._ShowAwacsInfo,self,cgrp) -local checkout=MENU_GROUP_COMMAND:New(cgrp,"Check Out",basemenu,self._CheckOut,self,cgrp) -local menus={ -groupname=cgrpname, -menuset=true, -basemenu=basemenu, -checkout=checkout, -picture=picture, -bogeydope=bogeydope, -declare=declare, -tasking=tasking, -showtask=showtask, -unable=unable, -abort=abort, -commit=commit, -tactical=tactical, -} -self.clientmenus:PullByID(cgrpname) -self.clientmenus:Push(menus,cgrpname) -end -elseif not self.clientmenus:HasUniqueID(cgrpname)then -local basemenu=MENU_GROUP:New(cgrp,self.Name,nil) -local checkin=MENU_GROUP_COMMAND:New(cgrp,"Check In",basemenu,self._CheckIn,self,cgrp) -checkin:SetTag(cgrp:GetName()) -basemenu:Refresh() -local menus={ -groupname=cgrpname, -menuset=false, -basemenu=basemenu, -checkin=checkin, -} -self.clientmenus:Push(menus,cgrpname) -end -end -else -if cgrp and cgrp:IsAlive()and not self.clientmenus:HasUniqueID(cgrpname)then -local basemenu=MENU_GROUP:New(cgrp,self.Name,nil) -local picture=MENU_GROUP_COMMAND:New(cgrp,"Picture",basemenu,self._Picture,self,cgrp) -local bogeydope=MENU_GROUP_COMMAND:New(cgrp,"Bogey Dope",basemenu,self._BogeyDope,self,cgrp) -local declare=MENU_GROUP_COMMAND:New(cgrp,"Declare",basemenu,self._Declare,self,cgrp) -local tasking=MENU_GROUP:New(cgrp,"Tasking",basemenu) -local showtask=MENU_GROUP_COMMAND:New(cgrp,"Showtask",tasking,self._Showtask,self,cgrp) -local commit=MENU_GROUP_COMMAND:New(cgrp,"Commit",tasking,self._Commit,self,cgrp) -local unable=MENU_GROUP_COMMAND:New(cgrp,"Unable",tasking,self._Unable,self,cgrp) -local abort=MENU_GROUP_COMMAND:New(cgrp,"Abort",tasking,self._TaskAbort,self,cgrp) -if self.AwacsROE==AWACS.ROE.POLICE or self.AwacsROE==AWACS.ROE.VID then -local vid=MENU_GROUP:New(cgrp,"VID as",tasking) -local hostile=MENU_GROUP_COMMAND:New(cgrp,"Hostile",vid,self._VID,self,cgrp,AWACS.IFF.ENEMY) -local neutral=MENU_GROUP_COMMAND:New(cgrp,"Neutral",vid,self._VID,self,cgrp,AWACS.IFF.NEUTRAL) -local friendly=MENU_GROUP_COMMAND:New(cgrp,"Friendly",vid,self._VID,self,cgrp,AWACS.IFF.FRIENDLY) -end -local ainfo=MENU_GROUP_COMMAND:New(cgrp,"Awacs Info",basemenu,self._ShowAwacsInfo,self,cgrp) -local checkin=MENU_GROUP_COMMAND:New(cgrp,"Check In",basemenu,self._CheckIn,self,cgrp) -local checkout=MENU_GROUP_COMMAND:New(cgrp,"Check Out",basemenu,self._CheckOut,self,cgrp) -basemenu:Refresh() -local menus={ -groupname=cgrpname, -menuset=true, -basemenu=basemenu, -checkin=checkin, -checkout=checkout, -picture=picture, -bogeydope=bogeydope, -declare=declare, -showtask=showtask, -tasking=tasking, -unable=unable, -abort=abort, -commit=commit, -} -self.clientmenus:Push(menus,cgrpname) -end -end -end -self.MonitoringData.Players=clientcount or 0 -self.MonitoringData.PlayersCheckedin=clientcheckedin or 0 -return self -end -function AWACS:_DeleteAnchorStackFromMarker(Name,Coord) -self:T(self.lid.."_DeleteAnchorStackFromMarker") -if self.AnchorStacks:HasUniqueID(Name)and self.PlayerStationName==Name then -local stack=self.AnchorStacks:ReadByID(Name) -local marker=stack.AnchorMarker -if stack.AnchorAssignedID:Count()==0 then -marker:Remove() -if self.debug then -stack.StationZone:UndrawZone() -end -self.AnchorStacks:PullByID(Name) -self.PlayerStationName=nil -else -if self.debug then -self:I(self.lid.."**** Cannot delete station, there are CAPs assigned!") -local text=marker:GetText() -marker:TextUpdate(text.."\nMarked for deletion") -end -end -end -return self -end -function AWACS:_MoveAnchorStackFromMarker(Name,Coord) -self:T(self.lid.."_MoveAnchorStackFromMarker") -if self.AnchorStacks:HasUniqueID(Name)and self.PlayerStationName==Name then -local station=self.AnchorStacks:PullByID(Name) -local stationtag=string.format("Station: %s\nCoordinate: %s",Name,Coord:ToStringLLDDM()) -local marker=station.AnchorMarker -local zone=station.StationZone -if self.debug then -zone:UndrawZone() -end -local radius=self.StationZone:GetRadius() -if radius<10000 then radius=10000 end -station.StationZone=ZONE_RADIUS:New(Name,Coord:GetVec2(),radius) -marker:UpdateCoordinate(Coord) -marker:UpdateText(stationtag) -station.AnchorMarker=marker -if self.debug then -station.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) -end -self.AnchorStacks:Push(station,Name) -end -return self -end -function AWACS:_CreateAnchorStackFromMarker(Name,Coord) -self:T(self.lid.."_CreateAnchorStackFromMarker") -local AnchorStackOne={} -AnchorStackOne.AnchorBaseAngels=self.AnchorBaseAngels -AnchorStackOne.Anchors=FIFO:New() -AnchorStackOne.AnchorAssignedID=FIFO:New() -local newname=Name -for i=1,self.AnchorMaxStacks do -AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels) -end -local radius=self.StationZone:GetRadius() -if radius<10000 then radius=10000 end -AnchorStackOne.StationZone=ZONE_RADIUS:New(newname,Coord:GetVec2(),radius) -AnchorStackOne.StationZoneCoordinate=Coord -AnchorStackOne.StationZoneCoordinateText=Coord:ToStringLLDDM() -AnchorStackOne.StationName=newname -if self.debug then -AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) -local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) -AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) -else -local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) -AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) -end -self.AnchorStacks:Push(AnchorStackOne,newname) -self.PlayerStationName=newname -return self -end -function AWACS:_CreateAnchorStack() -self:T(self.lid.."_CreateAnchorStack") -local stackscreated=self.AnchorStacks:GetSize() -if stackscreated==self.AnchorMaxAnchors then -return false,0 -end -local AnchorStackOne={} -AnchorStackOne.AnchorBaseAngels=self.AnchorBaseAngels -AnchorStackOne.Anchors=FIFO:New() -AnchorStackOne.AnchorAssignedID=FIFO:New() -local newname=self.StationZone:GetName() -for i=1,self.AnchorMaxStacks do -AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels) -end -if stackscreated==0 then -local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) -newname=self.StationZone:GetName().."-"..newsubname -AnchorStackOne.StationZone=self.StationZone -AnchorStackOne.StationZoneCoordinate=self.StationZone:GetCoordinate() -AnchorStackOne.StationZoneCoordinateText=self.StationZone:GetCoordinate():ToStringLLDDM() -AnchorStackOne.StationName=newname -if self.debug then -AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) -local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) -AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) -else -local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) -AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) -end -self.AnchorStacks:Push(AnchorStackOne,newname) -else -local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) -newname=self.StationZone:GetName().."-"..newsubname -local anchorbasecoord=self.OpsZone:GetCoordinate() -local anchorradius=anchorbasecoord:Get2DDistance(self.StationZone:GetCoordinate()) -local angel=self.StationZone:GetCoordinate():GetAngleDegrees(self.OpsZone:GetVec3()) -self:T("Angel Radians= "..angel) -local turn=math.fmod(self.AnchorTurn*stackscreated,360) -if self.AnchorTurn<0 then turn=-turn end -local newanchorbasecoord=anchorbasecoord:Translate(anchorradius,turn+angel) -local radius=self.StationZone:GetRadius() -if radius<10000 then radius=10000 end -AnchorStackOne.StationZone=ZONE_RADIUS:New(newname,newanchorbasecoord:GetVec2(),radius) -AnchorStackOne.StationZoneCoordinate=newanchorbasecoord -AnchorStackOne.StationZoneCoordinateText=newanchorbasecoord:ToStringLLDDM() -AnchorStackOne.StationName=newname -if self.debug then -AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) -local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) -AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) -else -local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) -AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) -end -self.AnchorStacks:Push(AnchorStackOne,newname) -end -return true,self.AnchorStacks:GetSize() -end -function AWACS:_GetFreeAnchorStack() -self:T(self.lid.."_GetFreeAnchorStack") -local AnchorStackNo,Free=0,false -local availablestacks=self.AnchorStacks:GetPointerStack()or{} -for _id,_entry in pairs(availablestacks)do -local entry=_entry -local data=entry.data -if data.Anchors:IsNotEmpty()then -AnchorStackNo=_id -Free=true -break -end -end -if not Free then -local created,number=self:_CreateAnchorStack() -if created then -self:_GetFreeAnchorStack() -end -end -return AnchorStackNo,Free -end -function AWACS:_AssignAnchorToID(GID,HasOwnStation,StationName) -self:T(self.lid.."_AssignAnchorToID") -if not HasOwnStation then -local AnchorStackNo,Free=self:_GetFreeAnchorStack() -if Free then -local Anchor=self.AnchorStacks:PullByPointer(AnchorStackNo) -local freeangels=Anchor.Anchors:Pull() -Anchor.AnchorAssignedID:Push(GID) -self.AnchorStacks:Push(Anchor,Anchor.StationName) -self:T({Anchor,freeangels}) -self:__AssignedAnchor(5,GID,Anchor,AnchorStackNo,freeangels) -else -self:E(self.lid.."Cannot assign free anchor stack to GID "..GID.." Trying again in 10secs.") -self:__AssignAnchor(10,GID) -end -else -local Anchor=self.AnchorStacks:PullByID(StationName) -local freeangels=Anchor.Anchors:Pull()or 25 -Anchor.AnchorAssignedID:Push(GID) -self.AnchorStacks:Push(Anchor,StationName) -self:T({Anchor,freeangels}) -local StackNo=self.AnchorStacks.stackbyid[StationName].pointer -self:__AssignedAnchor(5,GID,Anchor,StackNo,freeangels) -end -return self -end -function AWACS:_RemoveIDFromAnchor(GID,AnchorStackNo,Angels) -local gid=GID or 0 -local stack=AnchorStackNo or 0 -local angels=Angels or 0 -local debugstring=string.format("%s_RemoveIDFromAnchor for GID=%d Stack=%d Angels=%d",self.lid,gid,stack,angels) -self:T(debugstring) -if stack>0 and angels>0 then -local AnchorStackNo=AnchorStackNo or 1 -local Anchor=self.AnchorStacks:ReadByPointer(AnchorStackNo) -local removedID=Anchor.AnchorAssignedID:PullByID(GID) -Anchor.Anchors:Push(Angels) -end -return self -end -function AWACS:_StartIntel(awacs) -self:T(self.lid.."_StartIntel") -if self.intelstarted then return self end -self.DetectionSet:AddGroup(awacs) -local intel=INTEL:New(self.DetectionSet,self.coalition,self.callsigntxt) -intel:SetClusterAnalysis(true,false,false) -local acceptzoneset=SET_ZONE:New() -acceptzoneset:AddZone(self.ControlZone) -acceptzoneset:AddZone(self.OpsZone) -if not self.GCI then -self.OrbitZone:SetRadius(UTILS.NMToMeters(55)) -acceptzoneset:AddZone(self.OrbitZone) -end -if self.BorderZone then -acceptzoneset:AddZone(self.BorderZone) -end -intel:SetAcceptZones(acceptzoneset) -if self.NoHelos then -intel:SetFilterCategory({Unit.Category.AIRPLANE}) -else -intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) -end -local function NewCluster(Cluster) -self:__NewCluster(5,Cluster) -end -function intel:OnAfterNewCluster(From,Event,To,Cluster) -NewCluster(Cluster) -end -local function NewContact(Contact) -self:__NewContact(5,Contact) -end -function intel:OnAfterNewContact(From,Event,To,Contact) -NewContact(Contact) -end -local function LostContact(Contact) -self:__LostContact(5,Contact) -end -function intel:OnAfterLostContact(From,Event,To,Contact) -LostContact(Contact) -end -local function LostCluster(Cluster,Mission) -self:__LostCluster(5,Cluster,Mission) -end -function intel:OnAfterLostCluster(From,Event,To,Cluster,Mission) -LostCluster(Cluster,Mission) -end -self.intelstarted=true -intel.statusupdate=-30 -intel:__Start(5) -self.intel=intel -return self -end -function AWACS:_GetBlurredSize(size) -self:T(self.lid.."_GetBlurredSize") -local threatsize=0 -local blur=self.RadarBlur -local blurmin=100-blur -local blurmax=100+blur -local actblur=math.random(blurmin,blurmax)/100 -threatsize=math.floor(size*actblur) -if threatsize==0 then threatsize=1 end -if threatsize then end -local threatsizetext=AWACS.Shipsize[1] -if threatsize==2 then -threatsizetext=AWACS.Shipsize[2] -elseif threatsize==3 then -threatsizetext=AWACS.Shipsize[3] -elseif threatsize>3 then -threatsizetext=AWACS.Shipsize[4] -end -return threatsize,threatsizetext -end -function AWACS:_GetThreatLevelText(threatlevel) -self:T(self.lid.."_GetThreatLevelText") -local threattext="GREEN" -if threatlevel<=AWACS.THREATLEVEL.GREEN then -threattext="GREEN" -elseif threatlevel<=AWACS.THREATLEVEL.AMBER then -threattext="AMBER" -else -threattext="RED" -end -return threattext -end -function AWACS:_ToStringBR(FromCoordinate,ToCoordinate) -self:T(self.lid.."_ToStringBR") -local BRText="" -local BRTextTTS="" -local DirectionVec3=FromCoordinate:GetDirectionVec3(ToCoordinate) -local AngleRadians=FromCoordinate:GetAngleRadians(DirectionVec3) -local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),0) -local AngleDegText=string.format("%03d",AngleDegrees) -local AngleDegTextTTS="" -local zero=self.gettext:GetEntry("ZERO",self.locale) -local miles=self.gettext:GetEntry("MILES",self.locale) -AngleDegText=string.gsub(AngleDegText,"%d","%1 ") -AngleDegText=string.gsub(AngleDegText," $","") -AngleDegTextTTS=string.gsub(AngleDegText,"0",zero) -local Distance=ToCoordinate:Get2DDistance(FromCoordinate) -local distancenm=UTILS.Round(UTILS.MetersToNM(Distance),0) -BRText=string.format("%03d, %d %s",AngleDegrees,distancenm,miles) -BRTextTTS=string.format("%s, %d %s",AngleDegText,distancenm,miles) -if self.PathToGoogleKey then -BRTextTTS=string.format("%s, %d %s",AngleDegTextTTS,distancenm,miles) -end -self:T(BRText,BRTextTTS) -return BRText,BRTextTTS -end -function AWACS:_ToStringBRA(FromCoordinate,ToCoordinate,Altitude) -self:T(self.lid.."_ToStringBRA") -local BRText="" -local BRTextTTS="" -local altitude=UTILS.Round(UTILS.MetersToFeet(Altitude)/1000,0) -local DirectionVec3=FromCoordinate:GetDirectionVec3(ToCoordinate) -local AngleRadians=FromCoordinate:GetAngleRadians(DirectionVec3) -local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),0) -local AngleDegText=string.format("%03d",AngleDegrees) -AngleDegText=string.gsub(AngleDegText,"%d","%1 ") -AngleDegText=string.gsub(AngleDegText," $","") -local AngleDegTextTTS=string.gsub(AngleDegText,"0","zero") -local Distance=ToCoordinate:Get2DDistance(FromCoordinate) -local distancenm=UTILS.Round(UTILS.MetersToNM(Distance),0) -local zero=self.gettext:GetEntry("ZERO",self.locale) -local miles=self.gettext:GetEntry("MILES",self.locale) -local thsd=self.gettext:GetEntry("THOUSAND",self.locale) -local vlow=self.gettext:GetEntry("VERYLOW",self.locale) -if altitude>=1 then -BRText=string.format("%03d, %d %s, %d %s",AngleDegrees,distancenm,miles,altitude,thsd) -BRTextTTS=string.format("%s, %d %s, %d %s",AngleDegText,distancenm,miles,altitude,thsd) -if self.PathToGoogleKey then -BRTextTTS=string.format("%s, %d %s, %d %s",AngleDegTextTTS,distancenm,miles,altitude,thsd) -end -else -BRText=string.format("%03d, %d %s, %s",AngleDegrees,distancenm,miles,vlow) -BRTextTTS=string.format("%s, %d %s, %s",AngleDegText,distancenm,miles,vlow) -if self.PathToGoogleKey then -BRTextTTS=string.format("%s, %d %s, %s",AngleDegTextTTS,distancenm,miles,vlow) -end -end -self:T(BRText,BRTextTTS) -return BRText,BRTextTTS -end -function AWACS:_GetBRAfromBullsOrAO(clustercoordinate) -self:T(self.lid.."_GetBRAfromBullsOrAO") -local refcoord=self.AOCoordinate -local BRAText="" -local BRATextTTS="" -local bullsname=self.AOName or"Rock" -local stringbr,stringbrtts=self:_ToStringBR(refcoord,clustercoordinate) -BRAText=string.format("%s %s",bullsname,stringbr) -BRATextTTS=string.format("%s %s",bullsname,stringbrtts) -self:T(BRAText,BRATextTTS) -return BRAText,BRATextTTS -end -function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object,TaskStatus,Auftrag,Cluster,Contact) -self:T(self.lid.."_CreateTaskForGroup "..GroupID.." Description: "..Description) -local managedgroup=self.ManagedGrps[GroupID] -local task={} -self.ManagedTaskID=self.ManagedTaskID+1 -task.TID=self.ManagedTaskID -task.AssignedGroupID=GroupID -task.Status=TaskStatus or AWACS.TaskStatus.ASSIGNED -task.ToDo=Description -task.Auftrag=Auftrag -task.Cluster=Cluster -task.Contact=Contact -task.IsPlayerTask=managedgroup.IsPlayer -task.IsUnassigned=TaskStatus==AWACS.TaskStatus.UNASSIGNED and false or true -if Object and Object:IsInstanceOf("TARGET")then -task.Target=Object -else -task.Target=TARGET:New(Object) -end -task.ScreenText=ScreenText -if Description==AWACS.TaskDescription.ANCHOR or Description==AWACS.TaskDescription.REANCHOR then -task.Target.Type=TARGET.ObjectType.ZONE -end -task.RequestedTimestamp=timer.getTime() -self.ManagedTasks:Push(task,task.TID) -managedgroup.HasAssignedTask=true -managedgroup.CurrentTask=task.TID -self:T({managedgroup}) -self.ManagedGrps[GroupID]=managedgroup -return task.TID -end -function AWACS:_ReadAssignedTaskFromGID(GroupID) -self:T(self.lid.."_GetAssignedTaskFromGID "..GroupID) -local managedgroup=self.ManagedGrps[GroupID] -if managedgroup and managedgroup.HasAssignedTask and managedgroup.CurrentTask~=0 then -local TaskID=managedgroup.CurrentTask -if self.ManagedTasks:HasUniqueID(TaskID)then -return self.ManagedTasks:ReadByID(TaskID) -end -end -return nil -end -function AWACS:_ReadAssignedGroupFromTID(TaskID) -self:T(self.lid.."_ReadAssignedGroupFromTID "..TaskID) -if self.ManagedTasks:HasUniqueID(TaskID)then -local task=self.ManagedTasks:ReadByID(TaskID) -if task and task.AssignedGroupID and task.AssignedGroupID>0 then -return self.ManagedGrps[task.AssignedGroupID] -end -end -return nil -end -function AWACS:_MessageAIReadyForTasking(GID) -self:T(self.lid.."_MessageAIReadyForTasking") -if GID>0 and self.ManagedGrps[GID]then -local managedgroup=self.ManagedGrps[GID] -local GFCallsign=self:_GetCallSign(managedgroup.Group) -local aionst=self.gettext:GetEntry("AIONSTATION",self.locale) -local TextTTS=string.format(aionst,GFCallsign,self.callsigntxt,managedgroup.AnchorStackNo or 1,managedgroup.AnchorStackAngels or 25) -self:_NewRadioEntry(TextTTS,TextTTS,GID,false,false,true,true) -end -return self -end -function AWACS:_UpdateContactEngagementTag(CID,Text,TAC,MELD,TaskStatus) -self:T(self.lid.."_UpdateContactEngagementTag") -local text=Text or"" -local contact=self.Contacts:PullByID(CID) -if contact then -contact.EngagementTag=text -contact.TACCallDone=TAC or false -contact.MeldCallDone=MELD or false -contact.Status=TaskStatus or AWACS.TaskStatus.UNASSIGNED -self.Contacts:Push(contact,CID) -end -return self -end -function AWACS:_CheckTaskQueue() -self:T(self.lid.."_CheckTaskQueue") -local opentasks=0 -local assignedtasks=0 -for _id,_managedgroup in pairs(self.ManagedGrps)do -local group=_managedgroup -if group.Group and group.Group:IsAlive()then -local coordinate=group.Group:GetCoordinate() -if coordinate then -local NewCoordinate=COORDINATE:New(0,0,0) -group.LastKnownPosition=group.LastKnownPosition:UpdateFromCoordinate(coordinate) -self.ManagedGrps[_id]=group -end -end -end -if self.ManagedTasks:IsNotEmpty()then -opentasks=self.ManagedTasks:GetSize() -self:T("Assigned Tasks: "..opentasks) -local taskstack=self.ManagedTasks:GetPointerStack() -for _id,_entry in pairs(taskstack)do -local data=_entry -local entry=data.data -local target=entry.Target -local description=entry.ToDo -if description==AWACS.TaskDescription.ANCHOR or description==AWACS.TaskDescription.REANCHOR then -self:T("Open Task ANCHOR/REANCHOR") -local managedgroup=self.ManagedGrps[entry.AssignedGroupID] -if managedgroup then -local group=managedgroup.Group -if group and group:IsAlive()then -local groupcoord=group:GetCoordinate() -local zone=target:GetObject() -self:T({zone}) -if group:IsInZone(zone)then -self:T("Open Task ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID) -target:Stop() -if managedgroup.IsAI then -self:_MessageAIReadyForTasking(managedgroup.GID) -end -managedgroup.HasAssignedTask=false -self.ManagedGrps[entry.AssignedGroupID]=managedgroup -self.ManagedTasks:PullByID(entry.TID) -else -self:T("Open Task ANCHOR/REANCHOR executing for GroupID "..entry.AssignedGroupID) -end -else -self.ManagedTasks:PullByID(entry.TID) -end -end -elseif description==AWACS.TaskDescription.INTERCEPT then -self:T("Open Tasks INTERCEPT") -local taskstatus=entry.Status -local targetstatus=entry.Target:GetState() -if taskstatus==AWACS.TaskStatus.UNASSIGNED then -self.ManagedTasks:PullByID(entry.TID) -break -end -local managedgroup=self.ManagedGrps[entry.AssignedGroupID] -local targetgrp=entry.Contact.group -local position=entry.Contact.position or entry.Cluster.coordinate -if targetgrp and targetgrp:IsAlive()and managedgroup then -if position and managedgroup.Group and managedgroup.Group:IsAlive()then -local grouposition=managedgroup.Group:GetCoordinate()or managedgroup.Group:GetCoordinate() -local distance=1000 -if grouposition then -distance=grouposition:Get2DDistance(position) -distance=UTILS.Round(UTILS.MetersToNM(distance),0) -end -self:T("TAC/MELD distance check: "..distance.."NM!") -if distance<=self.TacDistance and distance>=self.MeldDistance then -self:T("TAC distance: "..distance.."NM!") -local Contact=self.Contacts:ReadByID(entry.Contact.CID) -self:_TACRangeCall(entry.AssignedGroupID,Contact) -elseif distance<=self.MeldDistance and distance>=self.ThreatDistance then -self:T("MELD distance: "..distance.."NM!") -local Contact=self.Contacts:ReadByID(entry.Contact.CID) -self:_MeldRangeCall(entry.AssignedGroupID,Contact) -end -end -end -local auftrag=entry.Auftrag -local auftragstatus="Not Known" -if auftrag then -auftragstatus=auftrag:GetState() -end -local text=string.format("ID=%d | Status=%s | TargetState=%s | AuftragState=%s",entry.TID,taskstatus,targetstatus,auftragstatus) -self:T(text) -if auftrag then -if auftrag:IsExecuting()then -entry.Status=AWACS.TaskStatus.EXECUTING -elseif auftrag:IsSuccess()then -entry.Status=AWACS.TaskStatus.SUCCESS -elseif auftrag:GetState()==AUFTRAG.Status.FAILED then -entry.Status=AWACS.TaskStatus.FAILED -end -if targetstatus=="Dead"then -entry.Status=AWACS.TaskStatus.SUCCESS -elseif targetstatus=="Alive"and auftrag:IsOver()then -entry.Status=AWACS.TaskStatus.FAILED -end -elseif entry.IsPlayerTask then -if entry.Target:IsDead()or entry.Target:IsDestroyed()or entry.Target:CountTargets()==0 then -entry.Status=AWACS.TaskStatus.SUCCESS -elseif entry.Target:IsAlive()then -local targetpos=entry.Target:GetCoordinate() -local outofzones=false -self.RejectZoneSet:ForEachZone( -function(Zone,Position) -local zone=Zone -local pos=Position -if pos and zone:IsVec2InZone(pos)then -outofzones=true -end -end, -targetpos:GetVec2() -) -if not outofzones then -outofzones=true -self.ZoneSet:ForEachZone( -function(Zone,Position) -local zone=Zone -local pos=Position -if pos and zone:IsVec2InZone(pos)then -outofzones=false -end -end, -targetpos:GetVec2() -) -end -if outofzones then -entry.Status=AWACS.TaskStatus.SUCCESS -end -end -end -if entry.Status==AWACS.TaskStatus.SUCCESS then -self:T("Open Tasks INTERCEPT success for GroupID "..entry.AssignedGroupID) -if managedgroup then -self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",true,true,AWACS.TaskStatus.SUCCESS) -managedgroup.HasAssignedTask=false -managedgroup.ContactCID=0 -managedgroup.LastTasking=timer.getTime() -if managedgroup.IsAI then -managedgroup.CurrentAuftrag=0 -else -managedgroup.CurrentTask=0 -end -self.ManagedGrps[entry.AssignedGroupID]=managedgroup -self.ManagedTasks:PullByID(entry.TID) -self:__InterceptSuccess(1) -self:__ReAnchor(5,managedgroup.GID) -end -elseif entry.Status==AWACS.TaskStatus.FAILED then -self:T("Open Tasks INTERCEPT failed for GroupID "..entry.AssignedGroupID) -if managedgroup then -managedgroup.HasAssignedTask=false -self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) -managedgroup.ContactCID=0 -managedgroup.LastTasking=timer.getTime() -if managedgroup.IsAI then -managedgroup.CurrentAuftrag=0 -else -managedgroup.CurrentTask=0 -end -if managedgroup.IsPlayer then -entry.IsPlayerTask=false -end -self.ManagedGrps[entry.AssignedGroupID]=managedgroup -if managedgroup.Group:IsAlive()or(managedgroup.FlightGroup and managedgroup.FlightGroup:IsAlive())then -self:__ReAnchor(5,managedgroup.GID) -end -end -self.ManagedTasks:PullByID(entry.TID) -self:__InterceptFailure(1) -elseif entry.Status==AWACS.TaskStatus.REQUESTED then -self:T("Open Tasks INTERCEPT REQUESTED for GroupID "..entry.AssignedGroupID) -local created=entry.RequestedTimestamp or timer.getTime()-120 -local Tnow=timer.getTime() -local Trunning=(Tnow-created)/60 -local text=string.format("Task TID %s Requested %d minutes ago.",entry.TID,Trunning) -if Trunning>self.ReassignmentPause then -entry.Status=AWACS.TaskStatus.UNASSIGNED -self.ManagedTasks:PullByID(entry.TID) -end -self:T(text) -end -elseif description==AWACS.TaskDescription.VID then -local managedgroup=self.ManagedGrps[entry.AssignedGroupID] -if(not managedgroup)or(not managedgroup.Group:IsAlive())then -self.ManagedTasks:PullByID(entry.TID) -return self -end -if entry.Target:IsDead()or entry.Target:IsDestroyed()or entry.Target:CountTargets()==0 then -entry.Status=AWACS.TaskStatus.SUCCESS -elseif entry.Target:IsAlive()then -self:T("Checking VID target out of bounds") -local targetpos=entry.Target:GetCoordinate() -local outofzones=false -self.RejectZoneSet:ForEachZone( -function(Zone,Position) -local zone=Zone -local pos=Position -if pos and zone:IsVec2InZone(pos)then -outofzones=true -end -end, -targetpos:GetVec2() -) -if not outofzones then -outofzones=true -self.ZoneSet:ForEachZone( -function(Zone,Position) -local zone=Zone -local pos=Position -if pos and zone:IsVec2InZone(pos)then -outofzones=false -end -end, -targetpos:GetVec2() -) -end -if outofzones then -entry.Status=AWACS.TaskStatus.SUCCESS -self:T("Out of bounds - SUCCESS") -end -end -if entry.Status==AWACS.TaskStatus.REQUESTED then -self:T("Open Tasks VID REQUESTED for GroupID "..entry.AssignedGroupID) -local created=entry.RequestedTimestamp or timer.getTime()-120 -local Tnow=timer.getTime() -local Trunning=(Tnow-created)/60 -local text=string.format("Task TID %s Requested %d minutes ago.",entry.TID,Trunning) -if Trunning>self.ReassignmentPause then -entry.Status=AWACS.TaskStatus.UNASSIGNED -self.ManagedTasks:PullByID(entry.TID) -end -self:T(text) -elseif entry.Status==AWACS.TaskStatus.ASSIGNED then -self:T("Open Tasks VID ASSIGNED for GroupID "..entry.AssignedGroupID) -local targetgrp=entry.Contact.group -local position=entry.Contact.position or entry.Cluster.coordinate -if targetgrp and targetgrp:IsAlive()and managedgroup then -if position and managedgroup.Group and managedgroup.Group:IsAlive()then -local grouposition=managedgroup.Group:GetCoordinate()or managedgroup.Group:GetCoordinate() -local distance=1000 -if grouposition then -distance=grouposition:Get2DDistance(position) -distance=UTILS.Round(UTILS.MetersToNM(distance),0) -end -self:T("TAC/MELD distance check: "..distance.."NM!") -if distance<=self.TacDistance and distance>=self.MeldDistance then -self:T("TAC distance: "..distance.."NM!") -local Contact=self.Contacts:ReadByID(entry.Contact.CID) -self:_TACRangeCall(entry.AssignedGroupID,Contact) -elseif distance<=self.MeldDistance and distance>=self.ThreatDistance then -self:T("MELD distance: "..distance.."NM!") -local Contact=self.Contacts:ReadByID(entry.Contact.CID) -self:_MeldRangeCall(entry.AssignedGroupID,Contact) -end -end -end -elseif entry.Status==AWACS.TaskStatus.SUCCESS then -self:T("Open Tasks VID success for GroupID "..entry.AssignedGroupID) -self.ManagedTasks:PullByID(entry.TID) -local Contact=self.Contacts:ReadByID(entry.Contact.CID) -if Contact and(Contact.IFF==AWACS.IFF.FRIENDLY or Contact.IFF==AWACS.IFF.NEUTRAL)then -self:T("IFF outcome friendly/neutral for GroupID "..entry.AssignedGroupID) -if managedgroup then -managedgroup.HasAssignedTask=false -self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) -managedgroup.ContactCID=0 -managedgroup.LastTasking=timer.getTime() -if managedgroup.IsAI then -managedgroup.CurrentAuftrag=0 -else -managedgroup.CurrentTask=0 -end -if managedgroup.IsPlayer then -entry.IsPlayerTask=false -end -self.ManagedGrps[entry.AssignedGroupID]=managedgroup -self:__ReAnchor(5,managedgroup.GID) -end -elseif Contact and Contact.IFF==AWACS.IFF.ENEMY then -self:T("IFF outcome hostile for GroupID "..entry.AssignedGroupID) -entry.ToDo=AWACS.TaskDescription.INTERCEPT -entry.Status=AWACS.TaskStatus.ASSIGNED -local cname=Contact.TargetGroupNaming -entry.ScreenText=string.format("Engage hostile %s group.",cname) -self.ManagedTasks:Push(entry,entry.TID) -local TextTTS=string.format("%s, %s. Engage hostile target!",managedgroup.CallSign,self.callsigntxt) -self:_NewRadioEntry(TextTTS,TextTTS,managedgroup.GID,true,self.debug,true,false,true) -elseif not Contact then -self:T("IFF outcome target DEAD for GroupID "..entry.AssignedGroupID) -if managedgroup then -managedgroup.HasAssignedTask=false -self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) -managedgroup.ContactCID=0 -managedgroup.LastTasking=timer.getTime() -if managedgroup.IsAI then -managedgroup.CurrentAuftrag=0 -else -managedgroup.CurrentTask=0 -end -if managedgroup.IsPlayer then -entry.IsPlayerTask=false -end -self.ManagedGrps[entry.AssignedGroupID]=managedgroup -if managedgroup.Group:IsAlive()or managedgroup.FlightGroup:IsAlive()then -self:__ReAnchor(5,managedgroup.GID) -end -end -end -elseif entry.Status==AWACS.TaskStatus.FAILED then -self:T("Open Tasks VID failed for GroupID "..entry.AssignedGroupID) -if managedgroup then -managedgroup.HasAssignedTask=false -self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) -managedgroup.ContactCID=0 -managedgroup.LastTasking=timer.getTime() -if managedgroup.IsAI then -managedgroup.CurrentAuftrag=0 -else -managedgroup.CurrentTask=0 -end -if managedgroup.IsPlayer then -entry.IsPlayerTask=false -end -self.ManagedGrps[entry.AssignedGroupID]=managedgroup -if managedgroup.Group:IsAlive()or managedgroup.FlightGroup:IsAlive()then -self:__ReAnchor(5,managedgroup.GID) -end -end -self.ManagedTasks:PullByID(entry.TID) -self:__InterceptFailure(1) -end -end -end -end -return self -end -function AWACS:_LogStatistics() -self:T(self.lid.."_LogStatistics") -local text=string.gsub(UTILS.OneLineSerialize(self.MonitoringData),",","\n") -local text=string.gsub(text,"{","\n") -local text=string.gsub(text,"}","") -local text=string.gsub(text,"="," = ") -self:T(text) -if self.MonitoringOn then -MESSAGE:New(text,20,"AWACS",false):ToAll() -end -return self -end -function AWACS:AddCAPAirWing(AirWing,Zone) -self:T(self.lid.."AddCAPAirWing") -if AirWing then -AirWing:SetUsingOpsAwacs(self) -local distance=self.AOCoordinate:Get2DDistance(AirWing:GetCoordinate()) -if Zone then -local stackscreated=self.AnchorStacks:GetSize() -if stackscreated==self.AnchorMaxAnchors then -self:E(self.lid.."Max number of stacks already created!") -else -local AnchorStackOne={} -AnchorStackOne.AnchorBaseAngels=self.AnchorBaseAngels -AnchorStackOne.Anchors=FIFO:New() -AnchorStackOne.AnchorAssignedID=FIFO:New() -local newname=Zone:GetName() -for i=1,self.AnchorMaxStacks do -AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels) -end -local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) -newname=Zone:GetName().."-"..newsubname -AnchorStackOne.StationZone=Zone -AnchorStackOne.StationZoneCoordinate=Zone:GetCoordinate() -AnchorStackOne.StationZoneCoordinateText=Zone:GetCoordinate():ToStringLLDDM() -AnchorStackOne.StationName=newname -if self.debug then -AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) -local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) -AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) -else -local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) -AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) -end -self.AnchorStacks:Push(AnchorStackOne,newname) -AirWing.HasOwnStation=true -AirWing.StationName=newname -end -end -self.CAPAirwings:Push(AirWing,distance) -end -return self -end -function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,ReportingName,Tactical) -self:T(self.lid.."_AnnounceContact") -local tag="" -local Tag=Tag -local CID=0 -if not Tag then -CID=Contact.CID or 0 -Tag=Contact.TargetGroupNaming or"" -end -if self.NoGroupTags then -Tag=nil -end -local isGroup=false -local GID=0 -local grpcallsign="Ghost 1" -if Group and Group:IsAlive()then -GID,isGroup,grpcallsign=self:_GetManagedGrpID(Group) -self:T("GID="..GID.." CheckedIn = "..tostring(isGroup)) -end -local cluster=Contact.Cluster -local intel=self.intel -local size=self.intel:ClusterCountUnits(cluster) -local threatsize,threatsizetext=self:_GetBlurredSize(size) -local clustercoordinate=Contact.Cluster.coordinate or Contact.Contact.position -local heading=Contact.Contact.group:GetHeading()or self.intel:CalcClusterDirection(cluster) -clustercoordinate:SetHeading(Contact.Contact.group:GetHeading()) -local BRAfromBulls,BRAfromBullsTTS=self:_GetBRAfromBullsOrAO(clustercoordinate) -self:T(BRAfromBulls) -self:T(BRAfromBullsTTS) -BRAfromBulls=BRAfromBulls.."." -BRAfromBullsTTS=BRAfromBullsTTS.."." -if isGroup then -BRAfromBulls=clustercoordinate:ToStringBRAANATO(Group:GetCoordinate(),true,true) -BRAfromBullsTTS=string.gsub(BRAfromBulls,"BRAA","brah") -BRAfromBullsTTS=string.gsub(BRAfromBullsTTS,"BRA","brah") -if self.PathToGoogleKey then -BRAfromBullsTTS=clustercoordinate:ToStringBRAANATO(Group:GetCoordinate(),true,true,true,false,true) -end -end -local BRAText="" -local TextScreen="" -if isGroup then -BRAText=string.format("%s, %s.",grpcallsign,self.callsigntxt) -TextScreen=string.format("%s, %s.",grpcallsign,self.callsigntxt) -else -BRAText=string.format("%s.",self.callsigntxt) -TextScreen=string.format("%s.",self.callsigntxt) -end -local newgrp=self.gettext:GetEntry("NEWGROUP",self.locale) -local grptxt=self.gettext:GetEntry("GROUP",self.locale) -local GRPtxt=self.gettext:GetEntry("GROUPCAP",self.locale) -local popup=self.gettext:GetEntry("POPUP",self.locale) -if IsNew and self.PlayerGuidance then -BRAText=string.format("%s %s.",BRAText,newgrp) -TextScreen=string.format("%s %s.",TextScreen,newgrp) -elseif IsPopup then -BRAText=string.format("%s %s %s.",BRAText,popup,grptxt) -TextScreen=string.format("%s %s %s.",TextScreen,popup,grptxt) -elseif IsBogeyDope and Tag and Tag~=""then -BRAText=string.format("%s %s %s.",BRAText,Tag,grptxt) -TextScreen=string.format("%s %s %s.",TextScreen,Tag,grptxt) -else -BRAText=string.format("%s %s.",BRAText,GRPtxt) -TextScreen=string.format("%s %s.",TextScreen,GRPtxt) -end -if not IsBogeyDope then -if Tag and Tag~=""then -BRAText=BRAText.." "..Tag.."." -TextScreen=TextScreen.." "..Tag.."." -end -end -if threatsize>1 then -BRAText=BRAText.." "..BRAfromBullsTTS.." "..threatsizetext.."." -TextScreen=TextScreen.." "..BRAfromBulls.." "..threatsizetext.."." -else -BRAText=BRAText.." "..BRAfromBullsTTS -TextScreen=TextScreen.." "..BRAfromBulls -end -if self.ModernEra then -local high=self.gettext:GetEntry("HIGH",self.locale) -local vfast=self.gettext:GetEntry("VERYFAST",self.locale) -local fast=self.gettext:GetEntry("FAST",self.locale) -if ReportingName and ReportingName~="Bogey"then -ReportingName=string.gsub(ReportingName,"_"," ") -BRAText=BRAText.." "..ReportingName.."." -TextScreen=TextScreen.." "..ReportingName.."." -end -local height=Contact.Contact.group:GetHeight() -local height=UTILS.Round(UTILS.MetersToFeet(height)/1000,0) -if height>=40 then -BRAText=BRAText..high -TextScreen=TextScreen..high -end -local speed=Contact.Contact.group:GetVelocityKNOTS() -if speed>900 then -BRAText=BRAText..vfast -TextScreen=TextScreen..vfast -elseif speed>=600 and speed<=900 then -BRAText=BRAText..fast -TextScreen=TextScreen..fast -end -end -BRAText=string.gsub(BRAText,"BRAA","brah") -BRAText=string.gsub(BRAText,"BRA","brah") -local prio=IsNew or IsBogeyDope -self:_NewRadioEntry(BRAText,TextScreen,GID,isGroup,true,IsNew,false,prio,Tactical) -return self -end -function AWACS:_GetAliveOpsGroupFromTable(OpsGroups) -self:T(self.lid.."_GetAliveOpsGroupFromTable") -local handback=nil -for _,_OG in pairs(OpsGroups or{})do -local OG=_OG -if OG and OG:IsAlive()then -handback=OG -break -end -end -return handback -end -function AWACS:_CleanUpAIMissionStack() -self:T(self.lid.."_CleanUpAIMissionStack") -local CAPMissions=0 -local Alert5Missions=0 -local InterceptMissions=0 -local MissionStack=FIFO:New() -self:T("Checking MissionStack") -for _,_mission in pairs(self.CatchAllMissions)do -local mission=_mission -local type=mission:GetType() -if type==AUFTRAG.Type.ALERT5 and mission:IsNotOver()then -MissionStack:Push(mission,mission.auftragsnummer) -Alert5Missions=Alert5Missions+1 -elseif type==AUFTRAG.Type.CAP and mission:IsNotOver()then -MissionStack:Push(mission,mission.auftragsnummer) -CAPMissions=CAPMissions+1 -elseif type==AUFTRAG.Type.INTERCEPT and mission:IsNotOver()then -MissionStack:Push(mission,mission.auftragsnummer) -InterceptMissions=InterceptMissions+1 -end -end -self.AICAPMissions=nil -self.AICAPMissions=MissionStack -return CAPMissions,Alert5Missions,InterceptMissions -end -function AWACS:_ConsistencyCheck() -self:T(self.lid.."_ConsistencyCheck") -if self.debug then -self:T("CatchAllMissions") -local catchallm={} -local report1=REPORT:New("CatchAll") -report1:Add("====================") -report1:Add("CatchAllMissions") -report1:Add("====================") -for _,_mission in pairs(self.CatchAllMissions)do -local mission=_mission -local nummer=mission.auftragsnummer or 0 -local type=mission:GetType() -local state=mission:GetState() -local FG=mission:GetOpsGroups() -local OG=self:_GetAliveOpsGroupFromTable(FG) -local OGName="UnknownFromMission" -if OG then -OGName=OG:GetName() -end -report1:Add(string.format("Auftrag Nr %d Type %s State %s FlightGroup %s",nummer,type,state,OGName)) -if mission:IsNotOver()then -catchallm[#catchallm+1]=mission -end -end -self.CatchAllMissions=nil -self.CatchAllMissions=catchallm -local catchallfg={} -self:T("CatchAllFGs") -report1:Add("====================") -report1:Add("CatchAllFGs") -report1:Add("====================") -for _,_fg in pairs(self.CatchAllFGs)do -local FG=_fg -local mission=FG:GetMissionCurrent() -local OGName=FG:GetName()or"UnknownFromFG" -local nummer=0 -local type="No Type" -local state="None" -if mission then -type=mission:GetType() -nummer=mission.auftragsnummer or 0 -state=mission:GetState() -end -report1:Add(string.format("Auftrag Nr %d Type %s State %s FlightGroup %s",nummer,type,state,OGName)) -if FG:IsAlive()then -catchallfg[#catchallfg+1]=FG -end -end -report1:Add("====================") -self:T(report1:Text()) -self.CatchAllFGs=nil -self.CatchAllFGs=catchallfg -end -return self -end -function AWACS:_CheckAICAPOnStation() -self:T(self.lid.."_CheckAICAPOnStation") -self:_ConsistencyCheck() -local capmissions,alert5missions,interceptmissions=self:_CleanUpAIMissionStack() -self:T("CAP="..capmissions.." ALERT5="..alert5missions.." Requested="..self.AIRequested) -if self.MaxAIonCAP>0 then -local onstation=capmissions+alert5missions -if capmissions>self.MaxAIonCAP then -self:T(string.format("*** Onstation %d > MaxAIOnCAP %d",onstation,self.MaxAIonCAP)) -local mission=self.AICAPMissions:Pull() -local Groups=mission:GetOpsGroups() -local OpsGroup=self:_GetAliveOpsGroupFromTable(Groups) -local GID,checkedin=self:_GetManagedGrpID(OpsGroup) -mission:__Cancel(30) -self.AIRequested=self.AIRequested-1 -if checkedin then -self:_CheckOut(OpsGroup,GID) -end -end -if capmissions0 then -local report=REPORT:New("CAP Mission Status") -report:Add("===============") -local missions=self.AICAPMissions:GetDataTable() -local i=1 -for _,_Mission in pairs(missions)do -local mission=_Mission -if mission then -i=i+1 -report:Add(string.format("Entry %d",i)) -report:Add(string.format("Mission No %d",mission.auftragsnummer)) -report:Add(string.format("Mission Type %s",mission:GetType())) -report:Add(string.format("Mission State %s",mission:GetState())) -local OpsGroups=mission:GetOpsGroups() -local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups) -if OpsGroup then -local OpsName=OpsGroup:GetName()or"Unknown" -local found,GID,OpsCallSign=self:_GetGIDFromGroupOrName(OpsGroup) -report:Add(string.format("Mission FG %s",OpsName)) -report:Add(string.format("Callsign %s",OpsCallSign)) -report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) -else -report:Add("***** Cannot obtain (yet) this missions OpsGroup!") -end -report:Add(string.format("Target Type %s",mission:GetTargetType())) -end -report:Add("===============") -end -if self.debug then -self:I(report:Text()) -end -end -end -return self -end -function AWACS:_SetAIROE(FlightGroup,Group) -self:T(self.lid.."_SetAIROE") -local ROE=self.AwacsROE or AWACS.ROE.POLICE -local ROT=self.AwacsROT or AWACS.ROT.PASSIVE -Group:OptionAlarmStateGreen() -Group:OptionECM_OnlyLockByRadar() -Group:OptionROEHoldFire() -Group:OptionROTEvadeFire() -Group:OptionRTBBingoFuel(true) -Group:OptionKeepWeaponsOnThreat() -local callname=self.AICAPCAllName or CALLSIGN.Aircraft.Colt -self.AICAPCAllNumber=self.AICAPCAllNumber+1 -Group:CommandSetCallsign(callname,math.fmod(self.AICAPCAllNumber,9)) -FlightGroup:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN) -FlightGroup:SetDefaultCallsign(callname,math.fmod(self.AICAPCAllNumber,9)) -if ROE==AWACS.ROE.POLICE or ROE==AWACS.ROE.VID then -FlightGroup:SetDefaultROE(ENUMS.ROE.WeaponHold) -elseif ROE==AWACS.ROE.IFF then -FlightGroup:SetDefaultROE(ENUMS.ROE.ReturnFire) -elseif ROE==AWACS.ROE.BVR then -FlightGroup:SetDefaultROE(ENUMS.ROE.OpenFire) -end -if ROT==AWACS.ROT.BYPASSESCAPE or ROT==AWACS.ROT.PASSIVE then -FlightGroup:SetDefaultROT(ENUMS.ROT.PassiveDefense) -elseif ROT==AWACS.ROT.OPENFIRE or ROT==AWACS.ROT.RETURNFIRE then -FlightGroup:SetDefaultROT(ENUMS.ROT.BypassAndEscape) -elseif ROT==AWACS.ROT.EVADE then -FlightGroup:SetDefaultROT(ENUMS.ROT.EvadeFire) -end -FlightGroup:SetFuelLowRTB(true) -FlightGroup:SetFuelLowThreshold(0.2) -FlightGroup:SetEngageDetectedOff() -FlightGroup:SetOutOfAAMRTB(true) -return self -end -function AWACS:_TACRangeCall(GID,Contact) -self:T(self.lid.."_TACRangeCall") -if not Contact then return self end -local pilotcallsign=self:_GetCallSign(nil,GID) -local managedgroup=self.ManagedGrps[GID] -local contact=Contact.Contact -local contacttag=Contact.TargetGroupNaming -if contact and not Contact.TACCallDone then -local position=contact.position -if position then -local distance=position:Get2DDistance(managedgroup.Group:GetCoordinate()) -distance=UTILS.Round(UTILS.MetersToNM(distance)) -local grptxt=self.gettext:GetEntry("GROUP",self.locale) -local miles=self.gettext:GetEntry("MILES",self.locale) -local text=string.format("%s. %s. %s %s, %d %s.",self.callsigntxt,pilotcallsign,contacttag,grptxt,distance,miles) -self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) -self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,false,AWACS.TaskStatus.EXECUTING) -end -end -return self -end -function AWACS:_MeldRangeCall(GID,Contact) -self:T(self.lid.."_MeldRangeCall") -if not Contact then return self end -local pilotcallsign=self:_GetCallSign(nil,GID) -local managedgroup=self.ManagedGrps[GID] -local flightpos=managedgroup.Group:GetCoordinate() -local contact=Contact.Contact -local contacttag=Contact.TargetGroupNaming -if contact and not Contact.MeldCallDone then -local position=contact.position -if position then -local BRATExt="" -if self.PathToGoogleKey then -BRATExt=position:ToStringBRAANATO(flightpos,false,false,true,false,true) -else -BRATExt=position:ToStringBRAANATO(flightpos,false,false) -end -local grptxt=self.gettext:GetEntry("GROUP",self.locale) -local text=string.format("%s. %s. %s %s, %s",self.callsigntxt,pilotcallsign,contacttag,grptxt,BRATExt) -self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) -self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,true,AWACS.TaskStatus.EXECUTING) -end -end -return self -end -function AWACS:_ThreatRangeCall(GID,Contact) -self:T(self.lid.."_ThreatRangeCall") -if not Contact then return self end -local pilotcallsign=self:_GetCallSign(nil,GID) -local managedgroup=self.ManagedGrps[GID] -local flightpos=managedgroup.Group:GetCoordinate()or managedgroup.LastKnownPosition -local contact=Contact.Contact -local contacttag=Contact.TargetGroupNaming -if contact then -local position=contact.position or contact.group:GetCoordinate() -if position then -local BRATExt="" -if self.PathToGoogleKey then -BRATExt=position:ToStringBRAANATO(flightpos,false,false,true,false,true) -else -BRATExt=position:ToStringBRAANATO(flightpos,false,false) -end -local grptxt=self.gettext:GetEntry("GROUP",self.locale) -local thrt=self.gettext:GetEntry("THREAT",self.locale) -local text=string.format("%s. %s. %s %s, %s. %s",self.callsigntxt,pilotcallsign,contacttag,grptxt,thrt,BRATExt) -self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) -end -end -return self -end -function AWACS:_MergedCall(GID) -self:T(self.lid.."_MergedCall") -local pilotcallsign=self:_GetCallSign(nil,GID) -local merge=self.gettext:GetEntry("MERGED",self.locale) -local text=string.format("%s. %s. %s.",self.callsigntxt,pilotcallsign,merge) -self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) -if GID and GID~=0 then -local managedgroup=self.ManagedGrps[GID] -if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then -local name=managedgroup.GroupName -if self.TacticalSubscribers[name]then -self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true) -end -end -end -return self -end -function AWACS:_AssignPilotToTarget(Pilots,Targets) -self:T(self.lid.."_AssignPilotToTarget") -local inreach=false -local Pilot=nil -local closest=UTILS.NMToMeters(self.maxassigndistance+1) -local targets=Targets:GetDataTable() -local Target=nil -for _,_target in pairs(targets)do -local targetgroupcoord=_target.Contact.position -for _,_Pilot in pairs(Pilots)do -local pilotcoord=_Pilot.Group:GetCoordinate() -local targetdist=targetgroupcoord:Get2DDistance(pilotcoord) -if UTILS.MetersToNM(targetdist) "..self.maxassigndistance.."NM! No Assignment!") -end -end -end -if inreach and Pilot and Pilot.IsPlayer then -local callsign=Pilot.CallSign -self.ManagedTasks:PullByID(Pilot.CurrentTask) -Pilot.HasAssignedTask=true -local TargetPosition=Target.Target:GetCoordinate() -local PlayerPositon=Pilot.LastKnownPosition -local TargetAlt=Target.Contact.altitude or Target.Cluster.altitude or Target.Contact.group:GetAltitude() -local TargetDirections,TargetDirectionsTTS=self:_ToStringBRA(PlayerPositon,TargetPosition,TargetAlt) -local ScreenText="" -local TaskType=AWACS.TaskDescription.INTERCEPT -if self.AwacsROE==AWACS.ROE.POLICE or self.AwacsROE==AWACS.ROE.VID then -local interc=self.gettext:GetEntry("SCREENVID",self.locale) -ScreenText=string.format(interc,Target.TargetGroupNaming) -TaskType=AWACS.TaskDescription.VID -else -local interc=self.gettext:GetEntry("SCREENINTER",self.locale) -ScreenText=string.format(interc,Target.TargetGroupNaming) -end -Pilot.CurrentTask=self:_CreateTaskForGroup(Pilot.GID,TaskType,ScreenText,Target.Target,AWACS.TaskStatus.REQUESTED,nil,Target.Cluster,Target.Contact) -Pilot.ContactCID=Target.CID -self.ManagedGrps[Pilot.GID]=Pilot -Target.LinkedTask=Pilot.CurrentTask -Target.LinkedGroup=Pilot.GID -Target.Status=AWACS.TaskStatus.REQUESTED -local targeted=self.gettext:GetEntry("ENGAGETAG",self.locale) -Target.EngagementTag=string.format(targeted,Pilot.CallSign) -self.Contacts:PullByID(Target.CID) -self.Contacts:Push(Target,Target.CID) -local reqcomm=self.gettext:GetEntry("REQCOMMIT",self.locale) -local text=string.format(reqcomm,self.callsigntxt,Target.TargetGroupNaming,TargetDirectionsTTS,Pilot.CallSign) -local textScreen=string.format(reqcomm,self.callsigntxt,Target.TargetGroupNaming,TargetDirections,Pilot.CallSign) -self:_NewRadioEntry(text,textScreen,Pilot.GID,true,self.debug,true,false,true) -elseif inreach and Pilot and Pilot.IsAI then -local callsign=Pilot.CallSign -local FGStatus=Pilot.FlightGroup:GetState() -self:T("Pilot AI Callsign: "..callsign) -self:T("Pilot FG State: "..FGStatus) -local targetstatus=Target.Target:GetState() -self:T("Target State: "..targetstatus) -local currmission=Pilot.FlightGroup:GetMissionCurrent() -if currmission then -self:T("Current Mission: "..currmission:GetType()) -end -local ZoneSet=self.ZoneSet -local RejectZoneSet=self.RejectZoneSet -local intercept=AUFTRAG:NewINTERCEPT(Target.Target) -intercept:SetWeaponExpend(AI.Task.WeaponExpend.ALL) -intercept:SetWeaponType(ENUMS.WeaponFlag.Auto) -intercept:AddConditionSuccess( -function(target,zoneset,rzoneset) -local success=true -local target=target -if target:IsDestroyed()or target:IsDead()or target:CountTargets()==0 then return true end -local tgtcoord=target:GetCoordinate() -local tgtvec2=nil -if tgtcoord then -tgtvec2=tgtcoord:GetVec2() -end -local zones=zoneset -local rzones=rzoneset -if tgtvec2 then -zones:ForEachZone( -function(zone) -if zone:IsVec2InZone(tgtvec2)then -success=false -end -end -) -rzones:ForEachZone( -function(zone) -if zone:IsVec2InZone(tgtvec2)then -success=true -end -end -) -end -return success -end, -Target.Target, -ZoneSet, -RejectZoneSet -) -Pilot.FlightGroup:AddMission(intercept) -local Angels=Pilot.AnchorStackAngels or 25 -Angels=Angels*1000 -local AnchorSpeed=self.CapSpeedBase or 270 -AnchorSpeed=UTILS.KnotsToAltKIAS(AnchorSpeed,Angels) -local Anchor=self.AnchorStacks:ReadByPointer(Pilot.AnchorStackNo) -local capauftrag=AUFTRAG:NewCAP(Anchor.StationZone,Angels,AnchorSpeed,Anchor.StationZoneCoordinate,0,15,{}) -capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) -Pilot.FlightGroup:AddMission(capauftrag) -if currmission then -currmission:__Cancel(3) -end -self.CatchAllMissions[#self.CatchAllMissions+1]=intercept -self.CatchAllMissions[#self.CatchAllMissions+1]=capauftrag -self.ManagedTasks:PullByID(Pilot.CurrentTask) -Pilot.HasAssignedTask=true -Pilot.CurrentTask=self:_CreateTaskForGroup(Pilot.GID,AWACS.TaskDescription.INTERCEPT,"Intercept Task",Target.Target,AWACS.TaskStatus.ASSIGNED,intercept,Target.Cluster,Target.Contact) -Pilot.CurrentAuftrag=intercept.auftragsnummer -Pilot.ContactCID=Target.CID -self.ManagedGrps[Pilot.GID]=Pilot -Target.LinkedTask=Pilot.CurrentTask -Target.LinkedGroup=Pilot.GID -Target.Status=AWACS.TaskStatus.ASSIGNED -local targeted=self.gettext:GetEntry("ENGAGETAG",self.locale) -Target.EngagementTag=string.format(targeted,Pilot.CallSign) -self.Contacts:PullByID(Target.CID) -self.Contacts:Push(Target,Target.CID) -local altitude=Target.Contact.altitude or Target.Contact.group:GetAltitude() -local position=Target.Cluster.coordinate or Target.Contact.position -if not position then -self.intel:GetClusterCoordinate(Target.Cluster,true) -end -local bratext,bratexttts=self:_ToStringBRA(Pilot.Group:GetCoordinate(),position,altitude or 8000) -local aicomm=self.gettext:GetEntry("AICOMMIT",self.locale) -local text=string.format(aicomm,self.callsigntxt,Target.TargetGroupNaming,bratexttts,Pilot.CallSign) -local textScreen=string.format(aicomm,self.callsigntxt,Target.TargetGroupNaming,bratext,Pilot.CallSign) -self:_NewRadioEntry(text,textScreen,Pilot.GID,true,self.debug,true,false,true) -local comm=self.gettext:GetEntry("COMMIT",self.locale) -local text=string.format("%s. %s.",Pilot.CallSign,comm) -self:_NewRadioEntry(text,text,Pilot.GID,true,self.debug,true,true,true) -self:__Intercept(2) -end -return self -end -function AWACS:onbeforeStart(From,Event,To) -self:T({From,Event,To}) -if self.IncludeHelicopters then -self.clientset:FilterCategories("helicopter") -end -return self -end -function AWACS:onafterStart(From,Event,To) -self:T({From,Event,To}) -local controlzonename="FEZ-"..self.AOName -self.ControlZone=ZONE_RADIUS:New(controlzonename,self.OpsZone:GetVec2(),UTILS.NMToMeters(self.ControlZoneRadius)) -if self.debug then -self.ControlZone:DrawZone(self.coalition,{0,1,0},1,{1,0,0},0.05,3,true) -self.OpsZone:DrawZone(self.coalition,{1,0,0},1,{1,0,0},0.2,5,true) -local AOCoordString=self.AOCoordinate:ToStringLLDDM() -local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) -MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) -self.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) -local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) -if not self.GCI then -MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) -self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true) -MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) -end -else -local AOCoordString=self.AOCoordinate:ToStringLLDDM() -local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) -MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) -if not self.GCI then -MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) -end -local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) -MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) -end -if not self.GCI then -local AwacsAW=self.AirWing -local mission=AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg) -local timeonstation=(self.AwacsTimeOnStation+self.ShiftChangeTime)*3600 -mission:SetTime(nil,timeonstation) -self.CatchAllMissions[#self.CatchAllMissions+1]=mission -AwacsAW:AddMission(mission) -self.AwacsMission=mission -self.AwacsInZone=false -self.AwacsReady=false -else -self.AwacsInZone=true -self.AwacsReady=true -self:_StartIntel(self.GCIGroup) -if self.GCIGroup:IsGround()then -self.AwacsFG=ARMYGROUP:New(self.GCIGroup) -self.AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation) -self.AwacsFG:SwitchRadio(self.Frequency,self.Modulation) -elseif self.GCIGroup:IsShip()then -self.AwacsFG=NAVYGROUP:New(self.GCIGroup) -self.AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation) -self.AwacsFG:SwitchRadio(self.Frequency,self.Modulation) -else -self:E(self.lid.."**** Group unsuitable for GCI ops! Needs to be a GROUND or SHIP type group!") -self:Stop() -return self -end -self.callsigntxt=string.format("%s",self.CallSignClear[self.CallSign]) -self:__CheckRadioQueue(-10) -local sunrise=self.gettext:GetEntry("SUNRISE",self.locale) -local text=string.format(sunrise,self.callsigntxt,self.callsigntxt) -self:_NewRadioEntry(text,text,0,false,false,false,false,true) -self:T(self.lid..text) -self.sunrisedone=true -end -local ZoneSet=SET_ZONE:New() -ZoneSet:AddZone(self.ControlZone) -if not self.GCI then -ZoneSet:AddZone(self.OrbitZone) -end -if self.BorderZone then -ZoneSet:AddZone(self.BorderZone) -end -local RejectZoneSet=SET_ZONE:New() -if self.RejectZone then -RejectZoneSet:AddZone(self.RejectZone) -end -self.ZoneSet=ZoneSet -self.RejectZoneSet=RejectZoneSet -if self.AllowMarkers then -local MarkerOps=MARKEROPS_BASE:New("AWACS",{"Station","Delete","Move"}) -local function Handler(Keywords,Coord,Text) -self:T(Text) -for _,_word in pairs(Keywords)do -if string.lower(_word)=="station"then -local Name=string.match(Text," ([%a]+)$") -self:_CreateAnchorStackFromMarker(Name,Coord) -break -elseif string.lower(_word)=="delete"then -local Name=string.match(Text," ([%a]+)$") -self:_DeleteAnchorStackFromMarker(Name,Coord) -break -elseif string.lower(_word)=="move"then -local Name=string.match(Text," ([%a]+)$") -self:_MoveAnchorStackFromMarker(Name,Coord) -break -end -end -end -function MarkerOps:OnAfterMarkAdded(From,Event,To,Text,Keywords,Coord) -Handler(Keywords,Coord,Text) -end -function MarkerOps:OnAfterMarkChanged(From,Event,To,Text,Keywords,Coord) -Handler(Keywords,Coord,Text) -end -function MarkerOps:OnAfterMarkDeleted(From,Event,To) -end -self.MarkerOps=MarkerOps -end -if self.GCI then -self:__Started(-5) -end -if self.TacticalMenu then -self:__CheckTacticalQueue(55) -end -self:__Status(-30) -return self -end -function AWACS:_CheckAwacsStatus() -self:T(self.lid.."_CheckAwacsStatus") -local awacs=nil -if self.AwacsFG then -awacs=self.AwacsFG:GetGroup() -end -local monitoringdata=self.MonitoringData -if not self.GCI then -if awacs and awacs:IsAlive()and not self.AwacsInZone then -local orbitzone=self.OrbitZone -if awacs:IsInZone(orbitzone)then -self.AwacsInZone=true -self:T(self.lid.."Arrived in Orbit Zone: "..orbitzone:GetName()) -local onstationtxt=self.gettext:GetEntry("AWONSTATION",self.locale) -local text=string.format(onstationtxt,self.callsigntxt,self.AOName or"Rock") -local textScreen=text -self:_NewRadioEntry(text,textScreen,0,false,true,true,false,true) -end -end -end -if(awacs and awacs:IsAlive())then -if not self.intelstarted then -local alt=UTILS.Round(UTILS.MetersToFeet(awacs:GetAltitude())/1000,0) -if alt>=10 then -self:_StartIntel(awacs) -end -end -if self.intelstarted and not self.sunrisedone then -local alt=UTILS.Round(UTILS.MetersToFeet(awacs:GetAltitude())/1000,0) -if alt>=10 then -local sunrise=self.gettext:GetEntry("SUNRISE",self.locale) -local text=string.format(sunrise,self.callsigntxt,self.callsigntxt) -self:_NewRadioEntry(text,text,0,false,false,false,false,true) -self:T(self.lid..text) -self.sunrisedone=true -end -end -local AWmission=self.AwacsMission -local awstatus=AWmission:GetState() -local AWmissiontime=(timer.getTime()-self.AwacsTimeStamp) -local AWTOSLeft=UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600)-AWmissiontime),0) -AWTOSLeft=UTILS.Round(AWTOSLeft/60,0) -local ChangeTime=UTILS.Round(((self.ShiftChangeTime*3600)/60),0) -local Changedue="No" -if not self.ShiftChangeAwacsFlag and(AWTOSLeft<=ChangeTime or AWmission:IsOver())then -Changedue="Yes" -self.ShiftChangeAwacsFlag=true -self:__AwacsShiftChange(2) -end -local report=REPORT:New("AWACS:") -report:Add("====================") -report:Add("AWACS:") -report:Add(string.format("Auftrag Status: %s",awstatus)) -report:Add(string.format("TOS Left: %d min",AWTOSLeft)) -report:Add(string.format("Needs ShiftChange: %s",Changedue)) -local OpsGroups=AWmission:GetOpsGroups() -local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups) -if OpsGroup then -local OpsName=OpsGroup:GetName()or"Unknown" -local OpsCallSign=OpsGroup:GetCallsignName()or"Unknown" -report:Add(string.format("Mission FG %s",OpsName)) -report:Add(string.format("Callsign %s",OpsCallSign)) -report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) -else -report:Add("***** Cannot obtain (yet) this missions OpsGroup!") -end -if self.ShiftChangeAwacsFlag and self.ShiftChangeAwacsRequested then -AWmission=self.AwacsMissionReplacement -local esstatus=AWmission:GetState() -local ESmissiontime=(timer.getTime()-self.AwacsTimeStamp) -local ESTOSLeft=UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600)-ESmissiontime),0) -ESTOSLeft=UTILS.Round(ESTOSLeft/60,0) -local ChangeTime=UTILS.Round(((self.ShiftChangeTime*3600)/60),0) -report:Add("AWACS REPLACEMENT:") -report:Add(string.format("Auftrag Status: %s",esstatus)) -report:Add(string.format("TOS Left: %d min",ESTOSLeft)) -local OpsGroups=AWmission:GetOpsGroups() -local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups) -if OpsGroup then -local OpsName=OpsGroup:GetName()or"Unknown" -local OpsCallSign=OpsGroup:GetCallsignName()or"Unknown" -report:Add(string.format("Mission FG %s",OpsName)) -report:Add(string.format("Callsign %s",OpsCallSign)) -report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) -else -report:Add("***** Cannot obtain (yet) this missions OpsGroup!") -end -if AWmission:IsExecuting()then -self.ShiftChangeAwacsFlag=false -self.ShiftChangeAwacsRequested=false -self.sunrisedone=false -if self.AwacsMission and self.AwacsMission:IsNotOver()then -self.AwacsMission:Cancel() -end -self.AwacsMission=self.AwacsMissionReplacement -self.AwacsMissionReplacement=nil -self.AwacsTimeStamp=timer.getTime() -report:Add("*** Replacement DONE ***") -end -report:Add("====================") -end -if self.HasEscorts then -for i=1,self.EscortNumber do -local ESmission=self.EscortMission[i] -if not ESmission then break end -local esstatus=ESmission:GetState() -local ESmissiontime=(timer.getTime()-self.EscortsTimeStamp) -local ESTOSLeft=UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600)-ESmissiontime),0) -ESTOSLeft=UTILS.Round(ESTOSLeft/60,0) -local ChangeTime=UTILS.Round(((self.ShiftChangeTime*3600)/60),0) -local Changedue="No" -if(ESTOSLeft<=ChangeTime and not self.ShiftChangeEscortsFlag)or(ESmission:IsOver()and not self.ShiftChangeEscortsFlag)then -Changedue="Yes" -self.ShiftChangeEscortsFlag=true -self:__EscortShiftChange(2) -end -report:Add("====================") -report:Add("ESCORTS:") -report:Add(string.format("Auftrag Status: %s",esstatus)) -report:Add(string.format("TOS Left: %d min",ESTOSLeft)) -report:Add(string.format("Needs ShiftChange: %s",Changedue)) -local OpsGroups=ESmission:GetOpsGroups() -local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups) -if OpsGroup then -local OpsName=OpsGroup:GetName()or"Unknown" -local OpsCallSign=OpsGroup:GetCallsignName()or"Unknown" -report:Add(string.format("Mission FG %s",OpsName)) -report:Add(string.format("Callsign %s",OpsCallSign)) -report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) -monitoringdata.EscortsStateMission[i]=esstatus -monitoringdata.EscortsStateFG[i]=OpsGroup:GetState() -else -report:Add("***** Cannot obtain (yet) this missions OpsGroup!") -end -report:Add("====================") -if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then -ESmission=self.EscortMissionReplacement[i] -local esstatus=ESmission:GetState() -local ESmissiontime=(timer.getTime()-self.EscortsTimeStamp) -local ESTOSLeft=UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600)-ESmissiontime),0) -ESTOSLeft=UTILS.Round(ESTOSLeft/60,0) -local ChangeTime=UTILS.Round(((self.ShiftChangeTime*3600)/60),0) -report:Add("ESCORTS REPLACEMENT:") -report:Add(string.format("Auftrag Status: %s",esstatus)) -report:Add(string.format("TOS Left: %d min",ESTOSLeft)) -local OpsGroups=ESmission:GetOpsGroups() -local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups) -if OpsGroup then -local OpsName=OpsGroup:GetName()or"Unknown" -local OpsCallSign=OpsGroup:GetCallsignName()or"Unknown" -report:Add(string.format("Mission FG %s",OpsName)) -report:Add(string.format("Callsign %s",OpsCallSign)) -report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) -else -report:Add("***** Cannot obtain (yet) this missions OpsGroup!") -end -if ESmission:IsExecuting()then -self.ShiftChangeEscortsFlag=false -self.ShiftChangeEscortsRequested=false -if ESmission and ESmission:IsNotOver()then -ESmission:Cancel() -end -self.EscortMission[i]=self.EscortMissionReplacement[i] -self.EscortMissionReplacement[i]=nil -self.EscortsTimeStamp=timer.getTime() -report:Add("*** Replacement DONE ***") -end -report:Add("====================") -end -end -end -if self.debug then -self:T(report:Text()) -end -else -local AWmission=self.AwacsMission -local awstatus=AWmission:GetState() -if AWmission:IsOver()then -self:I(self.lid.."*****AWACS is dead!*****") -self.ShiftChangeAwacsFlag=true -self:__AwacsShiftChange(2) -end -end -return monitoringdata -end -function AWACS:onafterStatus(From,Event,To) -self:T({From,Event,To}) -self:_SetClientMenus() -local monitoringdata=self.MonitoringData -if not self.GCI then -monitoringdata=self:_CheckAwacsStatus() -end -local awacsalive=false -if self.AwacsFG then -local awacs=self.AwacsFG:GetGroup() -if awacs and awacs:IsAlive()then -awacsalive=true -end -end -if self:Is("Running")and(awacsalive or self.AwacsInZone)then -if self.AwacsSRS then -self.AwacsSRS:SetCoordinate(self.AwacsFG:GetCoordinate()) -if self.TacticalSRS then -self.TacticalSRS:SetCoordinate(self.AwacsFG:GetCoordinate()) -end -end -self:_CheckAICAPOnStation() -self:_CleanUpContacts() -self:_CheckMerges() -self:_CheckSubscribers() -local outcome,targets=self:_TargetSelectionProcess(true) -self:_CheckTaskQueue() -local AI,Humans=self:_GetIdlePilots() -if outcome and#Humans>0 and self.PlayerCapAssignment then -self:_AssignPilotToTarget(Humans,targets) -end -if outcome and#AI>0 then -self:_AssignPilotToTarget(AI,targets) -end -end -if not self.GCI then -monitoringdata.AwacsShiftChange=self.ShiftChangeAwacsFlag -if self.AwacsFG then -monitoringdata.AwacsStateFG=self.AwacsFG:GetState() -end -monitoringdata.AwacsStateMission=self.AwacsMission:GetState() -monitoringdata.EscortsShiftChange=self.ShiftChangeEscortsFlag -end -monitoringdata.AICAPCurrent=self.AICAPMissions:Count() -monitoringdata.AICAPMax=self.MaxAIonCAP -monitoringdata.Airwings=self.CAPAirwings:Count() -self.MonitoringData=monitoringdata -if self.debug then -self:_LogStatistics() -end -local picturetime=timer.getTime()-self.PictureTimeStamp -if self.AwacsInZone and picturetime>self.PictureInterval then -self.PictureTimeStamp=timer.getTime() -self:_Picture(nil,true) -end -self:__Status(30) -return self -end -function AWACS:onafterStop(From,Event,To) -self:T({From,Event,To}) -self.intel:Stop() -local AWFiFo=self.CAPAirwings -local AWStack=AWFiFo:GetPointerStack() -for _ID,_AWID in pairs(AWStack)do -local SubAW=self.CAPAirwings:ReadByPointer(_ID) -if SubAW then -SubAW:RemoveUsingOpsAwacs() -end -end -self:UnHandleEvent(EVENTS.PlayerEnterAircraft) -self:UnHandleEvent(EVENTS.PlayerEnterUnit) -self:UnHandleEvent(EVENTS.PlayerLeaveUnit) -self:UnHandleEvent(EVENTS.Ejection) -self:UnHandleEvent(EVENTS.Crash) -self:UnHandleEvent(EVENTS.Dead) -self:UnHandleEvent(EVENTS.UnitLost) -self:UnHandleEvent(EVENTS.BDA) -self:UnHandleEvent(EVENTS.PilotDead) -self:UnHandleEvent(EVENTS.Shot) -return self -end -function AWACS:onafterAssignAnchor(From,Event,To,GID,HasOwnStation,StationName) -self:T({From,Event,To,"GID = "..GID}) -self:_AssignAnchorToID(GID,HasOwnStation,StationName) -return self -end -function AWACS:onafterCheckedOut(From,Event,To,GID,AnchorStackNo,Angels) -self:T({From,Event,To,"GID = "..GID}) -self:_RemoveIDFromAnchor(GID,AnchorStackNo,Angels) -return self -end -function AWACS:onafterAssignedAnchor(From,Event,To,GID,Anchor,AnchorStackNo,AnchorAngels) -self:T({From,Event,To,"GID="..GID,"Stack="..AnchorStackNo}) -local managedgroup=self.ManagedGrps[GID] -if not managedgroup then -self:E(self.lid.."**** GID "..GID.." Not Registered!") -return self -end -managedgroup.AnchorStackNo=AnchorStackNo -managedgroup.AnchorStackAngels=AnchorAngels -managedgroup.Blocked=false -local isPlayer=managedgroup.IsPlayer -local isAI=managedgroup.IsAI -local Group=managedgroup.Group -local CallSign=managedgroup.CallSign or"Ghost 1" -local AnchorName=Anchor.StationName or"unknown" -local AnchorCoordTxt=Anchor.StationZoneCoordinateText or"unknown" -local Angels=AnchorAngels or 25 -local AnchorSpeed=self.CapSpeedBase or 270 -local AuftragsNr=managedgroup.CurrentAuftrag -local textTTS="" -if self.PikesSpecialSwitch then -local stationtxt=self.gettext:GetEntry("STATIONAT",self.locale) -textTTS=string.format(stationtxt,CallSign,self.callsigntxt,AnchorName,Angels) -else -local stationtxt=self.gettext:GetEntry("STATIONATLONG",self.locale) -textTTS=string.format(stationtxt,CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed) -end -local ROEROT=self.AwacsROE..", "..self.AwacsROT -local stationtxtsc=self.gettext:GetEntry("STATIONSCREEN",self.locale) -local stationtxtta=self.gettext:GetEntry("STATIONTASK",self.locale) -local textScreen=string.format(stationtxtsc,CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) -local TextTasking=string.format(stationtxtta,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) -self:_NewRadioEntry(textTTS,textScreen,GID,isPlayer,isPlayer,true,false) -managedgroup.CurrentTask=self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,TextTasking,Anchor.StationZone) -if isAI then -local auftrag=managedgroup.FlightGroup:GetMissionCurrent() -if auftrag then -local auftragtype=auftrag:GetType() -if auftragtype==AUFTRAG.Type.ALERT5 then -local capauftrag=AUFTRAG:NewCAP(Anchor.StationZone,Angels*1000,AnchorSpeed,Anchor.StationZone:GetCoordinate(),0,15,{}) -capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) -capauftrag:AddAsset(managedgroup.FlightGroup) -self.CatchAllMissions[#self.CatchAllMissions+1]=capauftrag -managedgroup.FlightGroup:AddMission(capauftrag) -auftrag:Cancel() -else -self:E("**** AssignedAnchor but Auftrag NOT ALERT5!") -end -else -self:E("**** AssignedAnchor but NO Auftrag!") -end -end -self.ManagedGrps[GID]=managedgroup -return self -end -function AWACS:onafterNewCluster(From,Event,To,Cluster) -self:T({From,Event,To,Cluster.index}) -self.CID=self.CID+1 -self.Countactcounter=self.Countactcounter+1 -local ContactTable=Cluster.Contacts or{} -local function GetFirstAliveContact(table) -for _,_contact in pairs(table)do -local contact=_contact -if contact and contact.group and contact.group:IsAlive()then -return contact,contact.group -end -end -return nil -end -local Contact,Group=GetFirstAliveContact(ContactTable) -if not Contact then return self end -if Group and not Group:IsAirborne()then -return self -end -local targetset=SET_GROUP:New() -for _,_grp in pairs(ContactTable)do -local grp=_grp -targetset:AddGroup(grp.group,true) -end -local managedcontact={} -managedcontact.CID=self.CID -managedcontact.Contact=Contact -managedcontact.Cluster=Cluster -managedcontact.IFF=AWACS.IFF.BOGEY -managedcontact.Target=TARGET:New(targetset) -managedcontact.LinkedGroup=0 -managedcontact.LinkedTask=0 -managedcontact.Status=AWACS.TaskStatus.IDLE -local phoneid=math.fmod(self.Countactcounter,27) -if phoneid==0 then phoneid=1 end -managedcontact.TargetGroupNaming=AWACS.Phonetic[phoneid] -managedcontact.ReportingName=Contact.group:GetNatoReportingName() -managedcontact.TACCallDone=false -managedcontact.MeldCallDone=false -managedcontact.EngagementTag="" -local IsPopup=false -if self.OpsZone:IsVec2InZone(Contact.position:GetVec2())then -IsPopup=true -end -Contact.CID=managedcontact.CID -Contact.TargetGroupNaming=managedcontact.TargetGroupNaming -Cluster.CID=managedcontact.CID -Cluster.TargetGroupNaming=managedcontact.TargetGroupNaming -self.Contacts:Push(managedcontact,self.CID) -local ContactCoordinate=Contact.position:GetVec2() -local incontrolzone=self.ControlZone:IsVec2InZone(ContactCoordinate) -local distance=1000000 -if not self.GCI then -distance=Contact.position:Get2DDistance(self.OrbitZone:GetCoordinate()) -end -local inborderzone=false -if self.BorderZone then -inborderzone=self.BorderZone:IsVec2InZone(ContactCoordinate) -end -if incontrolzone or inborderzone or(distance<=UTILS.NMToMeters(55))or IsPopup then -self:_AnnounceContact(managedcontact,true,nil,false,managedcontact.TargetGroupNaming,IsPopup,managedcontact.ReportingName) -end -return self -end -function AWACS:onafterNewContact(From,Event,To,Contact) -self:T({From,Event,To,Contact}) -local tdist=self.ThreatDistance -for _gid,_mgroup in pairs(self.ManagedGrps)do -local managedgroup=_mgroup -local group=managedgroup.Group -if group and group:IsAlive()and group:IsAirborne()then -local cpos=Contact.position or Contact.group:GetCoordinate() -local mpos=group:GetCoordinate() -local dist=cpos:Get2DDistance(mpos) -dist=UTILS.Round(UTILS.MetersToNM(dist),0) -if dist<=tdist then -self:_ThreatRangeCall(_gid,Contact) -end -end -end -return self -end -function AWACS:onafterLostContact(From,Event,To,Contact) -self:T({From,Event,To,Contact}) -return self -end -function AWACS:onafterLostCluster(From,Event,To,Cluster,Mission) -self:T({From,Event,To}) -return self -end -function AWACS:onafterCheckTacticalQueue(From,Event,To) -self:T({From,Event,To}) -if self.clientset:CountAlive()==0 then -self:T(self.lid.."No player connected.") -self:__CheckTacticalQueue(-5) -return self -end -for _name,_freq in pairs(self.TacticalSubscribers)do -local Group=nil -if _name then -Group=GROUP:FindByName(_name) -end -if Group and Group:IsAlive()then -self:_BogeyDope(Group,true) -end -end -if(self.TacticalQueue:IsNotEmpty())then -while self.TacticalQueue:Count()>0 do -local RadioEntry=self.TacticalQueue:Pull() -self:T({RadioEntry}) -local frequency=self.TacticalBaseFreq -if RadioEntry.GroupID and RadioEntry.GroupID~=0 then -local managedgroup=self.ManagedGrps[RadioEntry.GroupID] -if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then -local name=managedgroup.GroupName -frequency=self.TacticalSubscribers[name] -end -end -local gtext=RadioEntry.TextTTS -if self.PathToGoogleKey then -gtext=string.format("%s",gtext) -end -self.TacticalSRSQ:NewTransmission(gtext,nil,self.TacticalSRS,nil,0.5,nil,nil,nil,frequency,self.TacticalModulation) -self:T(RadioEntry.TextTTS) -if RadioEntry.ToScreen and RadioEntry.TextScreen and(not self.SuppressScreenOutput)then -if RadioEntry.GroupID and RadioEntry.GroupID~=0 then -local managedgroup=self.ManagedGrps[RadioEntry.GroupID] -if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then -MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToGroup(managedgroup.Group) -self:T(RadioEntry.TextScreen) -end -else -MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToCoalition(self.coalition) -end -end -end -end -if self:Is("Running")then -self:__CheckTacticalQueue(-self.TacticalInterval) -end -return self -end -function AWACS:onafterCheckRadioQueue(From,Event,To) -self:T({From,Event,To}) -local nextcall=10 -if(self.RadioQueue:IsNotEmpty()or self.PrioRadioQueue:IsNotEmpty())then -local RadioEntry=nil -if self.PrioRadioQueue:IsNotEmpty()then -RadioEntry=self.PrioRadioQueue:Pull() -else -RadioEntry=self.RadioQueue:Pull() -end -self:T({RadioEntry}) -if self.clientset:CountAlive()==0 then -self:T(self.lid.."No player connected.") -self:__CheckRadioQueue(-5) -return self -end -if not RadioEntry.FromAI then -if self.PathToGoogleKey then -local gtext=RadioEntry.TextTTS -gtext=string.format("%s",gtext) -self.AwacsSRS:PlayTextExt(gtext,nil,self.MultiFrequency,self.MultiModulation,self.Gender,self.Culture,self.Voice,self.Volume,"AWACS") -else -self.AwacsSRS:PlayTextExt(RadioEntry.TextTTS,nil,self.MultiFrequency,self.MultiModulation,self.Gender,self.Culture,self.Voice,self.Volume,"AWACS") -end -self:T(RadioEntry.TextTTS) -else -if RadioEntry.GroupID and RadioEntry.GroupID~=0 then -local managedgroup=self.ManagedGrps[RadioEntry.GroupID] -if managedgroup and managedgroup.FlightGroup and managedgroup.FlightGroup:IsAlive()then -if self.PathToGoogleKey then -local gtext=RadioEntry.TextTTS -gtext=string.format("%s",gtext) -managedgroup.FlightGroup:RadioTransmission(gtext,1,false) -else -managedgroup.FlightGroup:RadioTransmission(RadioEntry.TextTTS,1,false) -end -self:T(RadioEntry.TextTTS) -end -end -end -if RadioEntry.Duration then nextcall=RadioEntry.Duration end -if RadioEntry.ToScreen and RadioEntry.TextScreen and(not self.SuppressScreenOutput)then -if RadioEntry.GroupID and RadioEntry.GroupID~=0 then -local managedgroup=self.ManagedGrps[RadioEntry.GroupID] -if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then -MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToGroup(managedgroup.Group) -self:T(RadioEntry.TextScreen) -end -else -MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToCoalition(self.coalition) -end -end -end -if self:Is("Running")then -if self.PathToGoogleKey then -nextcall=nextcall+self.GoogleTTSPadding -else -nextcall=nextcall+self.WindowsTTSPadding -end -self:__CheckRadioQueue(-nextcall) -end -return self -end -function AWACS:onafterEscortShiftChange(From,Event,To) -self:T({From,Event,To}) -if self.AwacsFG and self.ShiftChangeEscortsFlag and not self.ShiftChangeEscortsRequested then -local awacs=self.AwacsFG:GetGroup() -if awacs and awacs:IsAlive()then -self.ShiftChangeEscortsRequested=true -self.EscortsTimeStamp=timer.getTime() -self:_StartEscorts(true) -else -self:E("**** AWACS group dead at onafterEscortShiftChange!") -end -end -return self -end -function AWACS:onafterAwacsShiftChange(From,Event,To) -self:T({From,Event,To}) -if self.AwacsFG and self.ShiftChangeAwacsFlag and not self.ShiftChangeAwacsRequested then -self.ShiftChangeAwacsRequested=true -self.AwacsTimeStamp=timer.getTime() -local AwacsAW=self.AirWing -local mission=AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg) -self.CatchAllMissions[#self.CatchAllMissions+1]=mission -local timeonstation=(self.AwacsTimeOnStation+self.ShiftChangeTime)*3600 -mission:SetTime(nil,timeonstation) -AwacsAW:AddMission(mission) -self.AwacsMissionReplacement=mission -end -return self -end -function AWACS:onafterFlightOnMission(From,Event,To,FlightGroup,Mission) -self:T({From,Event,To}) -self:T("FlightGroup "..FlightGroup:GetName().." Mission "..Mission:GetName().." Type "..Mission:GetType()) -self.CatchAllFGs[#self.CatchAllFGs+1]=FlightGroup -if not self:Is("Stopped")then -if not self.AwacsReady or self.ShiftChangeAwacsFlag or self.ShiftChangeEscortsFlag then -self:_StartSettings(FlightGroup,Mission) -elseif Mission and(Mission:GetType()==AUFTRAG.Type.CAP or Mission:GetType()==AUFTRAG.Type.ALERT5 or Mission:GetType()==AUFTRAG.Type.ORBIT)then -if not self.FlightGroups:HasUniqueID(FlightGroup:GetName())then -self:T("Pushing FG "..FlightGroup:GetName().." to stack!") -self.FlightGroups:Push(FlightGroup,FlightGroup:GetName()) -end -end -end -return self -end -function AWACS:onafterReAnchor(From,Event,To,GID) -self:T({From,Event,To,GID}) -local managedgroup=self.ManagedGrps[GID] -if managedgroup then -if managedgroup.IsAI then -local AIFG=managedgroup.FlightGroup -if AIFG and AIFG:IsAlive()then -if AIFG:IsFuelLow()or AIFG:IsOutOfMissiles()or AIFG:IsOutOfAmmo()then -local destbase=AIFG.homebase -if not destbase then destbase=self.Airbase end -AIFG:RTB(destbase) -self:_CheckOut(AIFG:GetGroup(),GID) -self.AIRequested=self.AIRequested-1 -else -local Anchor=self.AnchorStacks:ReadByPointer(managedgroup.AnchorStackNo) -local StationZone=Anchor.StationZone -managedgroup.CurrentTask=self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,"Re-Station AI",StationZone) -managedgroup.HasAssignedTask=true -local mission=AIFG:GetMissionCurrent() -if mission then -managedgroup.CurrentAuftrag=mission.auftragsnummer or 0 -else -managedgroup.CurrentAuftrag=0 -end -managedgroup.ContactCID=0 -self.ManagedGrps[GID]=managedgroup -local tostation=self.gettext:GetEntry("VECTORSTATION",self.locale) -self:_MessageVector(GID,tostation,Anchor.StationZoneCoordinate,managedgroup.AnchorStackAngels) -end -else -local savedcallsign=managedgroup.CallSign -local textoptions={} -textoptions[1]=self.gettext:GetEntry("TEXTOPTIONS1",self.locale) -textoptions[2]=self.gettext:GetEntry("TEXTOPTIONS2",self.locale) -textoptions[3]=self.gettext:GetEntry("TEXTOPTIONS3",self.locale) -textoptions[4]=self.gettext:GetEntry("TEXTOPTIONS4",self.locale) -local allstations=self.gettext:GetEntry("ALLSTATIONS",self.locale) -local milestxt=self.gettext:GetEntry("MILES",self.locale) -if managedgroup.LastKnownPosition then -local lastknown=UTILS.DeepCopy(managedgroup.LastKnownPosition) -local faded=textoptions[math.random(1,4)] -local text=string.format("%s. %s. %s %s.",allstations,self.callsigntxt,faded,savedcallsign) -local textScreen=string.format("%s, %s. %s %s.",allstations,self.callsigntxt,faded,savedcallsign) -local brtext=self:_ToStringBULLS(lastknown) -local brtexttts=self:_ToStringBULLS(lastknown,false,true) -text=text.." "..brtexttts.." "..milestxt.."." -textScreen=textScreen.." "..brtext.." "..milestxt.."." -self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true) -end -self.ManagedGrps[GID]=nil -end -elseif managedgroup.IsPlayer then -local PLFG=managedgroup.Group -if PLFG and PLFG:IsAlive()then -local Anchor=self.AnchorStacks:ReadByPointer(managedgroup.AnchorStackNo) -local AnchorName=Anchor.StationName or"unknown" -local AnchorCoordTxt=Anchor.StationZoneCoordinateText or"unknown" -local Angels=managedgroup.AnchorStackAngels or 25 -local AnchorSpeed=self.CapSpeedBase or 270 -local StationZone=Anchor.StationZone -local ROEROT=self.AwacsROE.." "..self.AwacsROT -local stationtxt=self.gettext:GetEntry("STATIONTASK",self.locale) -local TextTasking=string.format(stationtxt,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) -managedgroup.CurrentTask=self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,TextTasking,StationZone) -managedgroup.HasAssignedTask=true -managedgroup.ContactCID=0 -self.ManagedGrps[GID]=managedgroup -local vectortxt=self.gettext:GetEntry("VECTORSTATION",self.locale) -self:_MessageVector(GID,vectortxt,Anchor.StationZoneCoordinate,managedgroup.AnchorStackAngels) -else -local savedcallsign=managedgroup.CallSign -local textoptions={} -textoptions[1]=self.gettext:GetEntry("TEXTOPTIONS1",self.locale) -textoptions[2]=self.gettext:GetEntry("TEXTOPTIONS2",self.locale) -textoptions[3]=self.gettext:GetEntry("TEXTOPTIONS3",self.locale) -textoptions[4]=self.gettext:GetEntry("TEXTOPTIONS4",self.locale) -local allstations=self.gettext:GetEntry("ALLSTATIONS",self.locale) -local milestxt=self.gettext:GetEntry("MILES",self.locale) -local faded=textoptions[math.random(1,4)] -local text=string.format("%s. %s. %s %s.",allstations,self.callsigntxt,faded,savedcallsign) -local textScreen=string.format("%s, %s. %s %s.",allstations,self.callsigntxt,faded,savedcallsign) -if managedgroup.LastKnownPosition then -local lastknown=UTILS.DeepCopy(managedgroup.LastKnownPosition) -local brtext=self:_ToStringBULLS(lastknown) -local brtexttts=self:_ToStringBULLS(lastknown,false,true) -text=text.." "..brtexttts.." "..milestxt.."." -textScreen=textScreen.." "..brtext.." "..milestxt.."." -self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true) -end -self.ManagedGrps[GID]=nil -end -end -end -end -end -BRIGADE={ -ClassName="BRIGADE", -verbose=0, -rearmingZones={}, -refuellingZones={}, -} -BRIGADE.version="0.1.1" -function BRIGADE:New(WarehouseName,BrigadeName) -local self=BASE:Inherit(self,LEGION:New(WarehouseName,BrigadeName)) -if not self then -BASE:E(string.format("ERROR: Could not find warehouse %s!",WarehouseName)) -return nil -end -self.lid=string.format("BRIGADE %s | ",self.alias) -self:SetRetreatZones() -if self:IsShip()then -local wh=self.warehouse -local group=wh:GetGroup() -self.warehouseOpsGroup=NAVYGROUP:New(group) -self.warehouseOpsElement=self.warehouseOpsGroup:GetElementByName(wh:GetName()) -end -self:AddTransition("*","ArmyOnMission","*") -return self -end -function BRIGADE:AddPlatoon(Platoon) -table.insert(self.cohorts,Platoon) -self:AddAssetToPlatoon(Platoon,Platoon.Ngroups) -Platoon:SetBrigade(self) -if Platoon:IsStopped()then -Platoon:Start() -end -return self -end -function BRIGADE:AddAssetToPlatoon(Platoon,Nassets) -if Platoon then -local Group=GROUP:FindByName(Platoon.templatename) -if Group then -local text=string.format("Adding asset %s to platoon %s",Group:GetName(),Platoon.name) -self:T(self.lid..text) -self:AddAsset(Group,Nassets,nil,nil,nil,nil,Platoon.skill,Platoon.livery,Platoon.name) -else -self:E(self.lid.."ERROR: Group does not exist!") -end -else -self:E(self.lid.."ERROR: Platoon does not exit!") -end -return self -end -function BRIGADE:SetRetreatZones(RetreatZoneSet) -self.retreatZones=RetreatZoneSet or SET_ZONE:New() -return self -end -function BRIGADE:AddRetreatZone(RetreatZone) -self.retreatZones:AddZone(RetreatZone) -return self -end -function BRIGADE:GetRetreatZones() -return self.retreatZones -end -function BRIGADE:AddRearmingZone(RearmingZone) -local rearmingzone={} -rearmingzone.zone=RearmingZone -rearmingzone.mission=nil -rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(),"Rearming Zone"):ToCoalition(self:GetCoalition()) -table.insert(self.rearmingZones,rearmingzone) -return rearmingzone -end -function BRIGADE:AddRefuellingZone(RefuellingZone) -local supplyzone={} -supplyzone.zone=RefuellingZone -supplyzone.mission=nil -supplyzone.marker=MARKER:New(supplyzone.zone:GetCoordinate(),"Refuelling Zone"):ToCoalition(self:GetCoalition()) -table.insert(self.refuellingZones,supplyzone) -return supplyzone -end -function BRIGADE:GetPlatoon(PlatoonName) -local platoon=self:_GetCohort(PlatoonName) -return platoon -end -function BRIGADE:GetPlatoonOfAsset(Asset) -local platoon=self:GetPlatoon(Asset.squadname) -return platoon -end -function BRIGADE:RemoveAssetFromPlatoon(Asset) -local platoon=self:GetPlatoonOfAsset(Asset) -if platoon then -platoon:DelAsset(Asset) -end -end -function BRIGADE:LoadBackAssetInPosition(Templatename,Position) -self:T(self.lid.."LoadBackAssetInPosition: "..tostring(Templatename)) -local nametbl=UTILS.Split(Templatename,"_") -local name=nametbl[1] -self:T(string.format("*** Target Platoon = %s ***",name)) -local cohorts=self.cohorts or{} -local thisasset=nil -local found=false -for _,_cohort in pairs(cohorts)do -local asset=_cohort:GetName() -self:T(string.format("*** Looking at Platoon = %s ***",asset)) -if asset==name then -self:T("**** Found Platoon ****") -local cohassets=_cohort.assets or{} -for _,_zug in pairs(cohassets)do -local zug=_zug -if zug.assignment==name and zug.requested==false then -self:T("**** Found Asset ****") -found=true -thisasset=zug -break -end -end -end -end -if found then -thisasset.rid=thisasset.uid -thisasset.requested=false -thisasset.score=100 -thisasset.missionTask="CAS" -thisasset.spawned=true -local template=thisasset.templatename -local alias=thisasset.spawngroupname -local spawnasset=SPAWN:NewWithAlias(template,alias) -:InitDelayOff() -:SpawnFromCoordinate(Position) -local request={} -request.assignment=name -request.warehouse=self -request.assets={thisasset} -request.ntransporthome=0 -request.ndelivered=0 -request.ntransport=0 -request.cargoattribute=thisasset.attribute -request.category=thisasset.category -request.cargoassets={thisasset} -request.assetdesc=WAREHOUSE.Descriptor.ASSETLIST -request.cargocategory=thisasset.category -request.toself=true -request.transporttype=WAREHOUSE.TransportType.SELFPROPELLED -request.assetproblem={} -request.born=true -request.prio=50 -request.uid=thisasset.uid -request.airbase=nil -request.timestamp=timer.getAbsTime() -request.assetdescval={thisasset} -request.nasset=1 -request.cargogroupset=SET_GROUP:New() -request.cargogroupset:AddGroup(spawnasset) -request.iscargo=true -self:__AssetSpawned(2,spawnasset,thisasset,request) -end -return self -end -function BRIGADE:onafterStart(From,Event,To) -self:GetParent(self,BRIGADE).onafterStart(self,From,Event,To) -self:I(self.lid..string.format("Starting BRIGADE v%s",BRIGADE.version)) -end -function BRIGADE:onafterStatus(From,Event,To) -self:GetParent(self).onafterStatus(self,From,Event,To) -local fsmstate=self:GetState() -self:CheckTransportQueue() -self:CheckMissionQueue() -for _,_rearmingzone in pairs(self.rearmingZones)do -local rearmingzone=_rearmingzone -if(not rearmingzone.mission)or rearmingzone.mission:IsOver()then -rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone) -self:AddMission(rearmingzone.mission) -end -end -for _,_supplyzone in pairs(self.refuellingZones)do -local supplyzone=_supplyzone -if(not supplyzone.mission)or supplyzone.mission:IsOver()then -supplyzone.mission=AUFTRAG:NewFUELSUPPLY(supplyzone.zone) -self:AddMission(supplyzone.mission) -end -end -if self.verbose>=1 then -local Nmissions=self:CountMissionsInQueue() -local Npq,Np,Nq=self:CountAssetsOnMission() -local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]",self:CountAssets(),Npq,Np,Nq) -local text=string.format("%s: Missions=%d, Platoons=%d, Assets=%s",fsmstate,Nmissions,#self.cohorts,assets) -self:I(self.lid..text) -end -if self.verbose>=2 then -local text=string.format("Missions Total=%d:",#self.missionqueue) -for i,_mission in pairs(self.missionqueue)do -local mission=_mission -local prio=string.format("%d/%s",mission.prio,tostring(mission.importance));if mission.urgent then prio=prio.." (!)"end -local assets=string.format("%d/%d",mission:CountOpsGroups(),mission.Nassets or 0) -local target=string.format("%d/%d Damage=%.1f",mission:CountMissionTargets(),mission:GetTargetInitialNumber(),mission:GetTargetDamage()) -text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s",i,mission.name,mission.type,mission.status,prio,assets,target) -end -self:I(self.lid..text) -end -if self.verbose>=2 then -local text=string.format("Transports Total=%d:",#self.transportqueue) -for i,_transport in pairs(self.transportqueue)do -local transport=_transport -local prio=string.format("%d/%s",transport.prio,tostring(transport.importance));if transport.urgent then prio=prio.." (!)"end -local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d",transport.Ncargo,transport.Ndelivered,transport.Ncarrier) -text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s",i,transport.uid,transport:GetState(),prio,carriers) -end -self:I(self.lid..text) -end -if self.verbose>=3 then -local text="Platoons:" -for i,_platoon in pairs(self.cohorts)do -local platoon=_platoon -local callsign=platoon.callsignName and UTILS.GetCallsignName(platoon.callsignName)or"N/A" -local modex=platoon.modex and platoon.modex or-1 -local skill=platoon.skill and tostring(platoon.skill)or"N/A" -text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s",platoon.name,platoon:GetState(),platoon.aircrafttype,platoon:CountAssets(true),#platoon.assets,callsign,modex,skill) -end -self:I(self.lid..text) -end -if self.verbose>=4 then -local text="Rearming Zones:" -for i,_rearmingzone in pairs(self.rearmingZones)do -local rearmingzone=_rearmingzone -text=text..string.format("\n* %s: Mission status=%s, suppliers=%d",rearmingzone.zone:GetName(),rearmingzone.mission:GetState(),rearmingzone.mission:CountOpsGroups()) -end -self:I(self.lid..text) -end -if self.verbose>=4 then -local text="Refuelling Zones:" -for i,_refuellingzone in pairs(self.refuellingZones)do -local refuellingzone=_refuellingzone -text=text..string.format("\n* %s: Mission status=%s, suppliers=%d",refuellingzone.zone:GetName(),refuellingzone.mission:GetState(),refuellingzone.mission:CountOpsGroups()) -end -self:I(self.lid..text) -end -if self.verbose>=5 then -local text="Assets in stock:" -for i,_asset in pairs(self.stock)do -local asset=_asset -text=text..string.format("\n* %s: spawned=%s",asset.spawngroupname,tostring(asset.spawned)) -end -self:I(self.lid..text) -end -end -function BRIGADE:onafterArmyOnMission(From,Event,To,ArmyGroup,Mission) -self:T(self.lid..string.format("Group %s on %s mission %s",ArmyGroup:GetName(),Mission:GetType(),Mission:GetName())) -end -CHIEF={ -ClassName="CHIEF", -verbose=0, -lid=nil, -targetqueue={}, -zonequeue={}, -borderzoneset=nil, -yellowzoneset=nil, -engagezoneset=nil, -tacview=false, -Nsuccess=0, -Nfailure=0, -} -CHIEF.DEFCON={ -GREEN="Green", -YELLOW="Yellow", -RED="Red", -} -CHIEF.Strategy={ -PASSIVE="Passive", -DEFENSIVE="Defensive", -OFFENSIVE="Offensive", -AGGRESSIVE="Aggressive", -TOTALWAR="Total War" -} -CHIEF.version="0.6.0" -function CHIEF:New(Coalition,AgentSet,Alias) -Alias=Alias or"CHIEF" -if type(Coalition)=="string"then -if string.lower(Coalition)=="blue"then -Coalition=coalition.side.BLUE -elseif string.lower(Coalition)=="red"then -Coalition=coalition.side.RED -else -Coalition=coalition.side.NEUTRAL -end -end -local self=BASE:Inherit(self,INTEL:New(AgentSet,Coalition,Alias)) -self:SetBorderZones() -self:SetConflictZones() -self:SetAttackZones() -self:SetThreatLevelRange() -self.Defcon=CHIEF.DEFCON.GREEN -self.strategy=CHIEF.Strategy.DEFENSIVE -self.TransportCategories={Group.Category.HELICOPTER} -self.commander=COMMANDER:New(Coalition) -self:AddTransition("*","MissionAssign","*") -self:AddTransition("*","MissionCancel","*") -self:AddTransition("*","TransportCancel","*") -self:AddTransition("*","OpsOnMission","*") -self:AddTransition("*","ZoneCaptured","*") -self:AddTransition("*","ZoneLost","*") -self:AddTransition("*","ZoneEmpty","*") -self:AddTransition("*","ZoneAttacked","*") -self:AddTransition("*","DefconChange","*") -self:AddTransition("*","StrategyChange","*") -self:AddTransition("*","LegionLost","*") -return self -end -function CHIEF:SetAirToAny() -self:SetFilterCategory({}) -return self -end -function CHIEF:SetAirToAir() -self:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) -return self -end -function CHIEF:SetAirToGround() -self:SetFilterCategory({Unit.Category.GROUND_UNIT}) -return self -end -function CHIEF:SetAirToSea() -self:SetFilterCategory({Unit.Category.SHIP}) -return self -end -function CHIEF:SetAirToSurface() -self:SetFilterCategory({Unit.Category.GROUND_UNIT,Unit.Category.SHIP}) -return self -end -function CHIEF:SetThreatLevelRange(ThreatLevelMin,ThreatLevelMax) -self.threatLevelMin=ThreatLevelMin or 1 -self.threatLevelMax=ThreatLevelMax or 10 -return self -end -function CHIEF:SetDefcon(Defcon) -local gotit=false -for _,defcon in pairs(CHIEF.DEFCON)do -if defcon==Defcon then -gotit=true -end -end -if not gotit then -self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s",tostring(Defcon))) -return self -end -if Defcon~=self.Defcon then -self:DefconChange(Defcon) -end -self.Defcon=Defcon -return self -end -function CHIEF:CreateResource(MissionType,Nmin,Nmax,Attributes,Properties,Categories) -local resources={} -local resource=self:AddToResource(resources,MissionType,Nmin,Nmax,Attributes,Properties,Categories) -return resources,resource -end -function CHIEF:AddToResource(Resource,MissionType,Nmin,Nmax,Attributes,Properties,Categories) -local resource={} -resource.MissionType=MissionType -resource.Nmin=Nmin or 1 -resource.Nmax=Nmax or Nmin -resource.Attributes=UTILS.EnsureTable(Attributes,true) -resource.Properties=UTILS.EnsureTable(Properties,true) -resource.Categories=UTILS.EnsureTable(Categories,true) -resource.carrierNmin=nil -resource.carrierNmax=nil -resource.carrierAttributes=nil -resource.carrierProperties=nil -resource.carrierCategories=nil -table.insert(Resource,resource) -if self.verbose>10 then -local text="Resource:" -for _,_r in pairs(Resource)do -local r=_r -text=text..string.format("\nmission=%s, Nmin=%d, Nmax=%d, attribute=%s, properties=%s",r.MissionType,r.Nmin,r.Nmax,tostring(r.Attributes[1]),tostring(r.Properties[1])) -end -self:I(self.lid..text) -end -return resource -end -function CHIEF:AddTransportToResource(Resource,Nmin,Nmax,CarrierAttributes,CarrierProperties,CarrierCategories) -Resource.carrierNmin=Nmin or 1 -Resource.carrierNmax=Nmax or Nmin -Resource.carrierCategories=UTILS.EnsureTable(CarrierCategories,true) -Resource.carrierAttributes=UTILS.EnsureTable(CarrierAttributes,true) -Resource.carrierProperties=UTILS.EnsureTable(CarrierProperties,true) -return self -end -function CHIEF:DeleteFromResource(Resource,MissionType) -for i=#Resource,1,-1 do -local resource=Resource[i] -if resource.MissionType==MissionType then -if resource.mission and resource.mission:IsNotOver()then -resource.mission:Cancel() -end -table.remove(Resource,i) -end -end -return self -end -function CHIEF:SetResponseOnTarget(NassetsMin,NassetsMax,ThreatLevel,TargetCategory,MissionType,Nunits,Defcon,Strategy) -local bla={} -bla.nAssetMin=NassetsMin or 1 -bla.nAssetMax=NassetsMax or bla.nAssetMin -bla.threatlevel=ThreatLevel or 0 -bla.targetCategory=TargetCategory -bla.missionType=MissionType -bla.nUnits=Nunits or 1 -bla.defcon=Defcon -bla.strategy=Strategy -self.assetNumbers=self.assetNumbers or{} -table.insert(self.assetNumbers,bla) -end -function CHIEF:_GetAssetsForTarget(Target,MissionType) -local threatlevel=Target:GetThreatLevelMax() -local nUnits=Target.N0 -local targetcategory=Target:GetCategory() -self:T(self.lid..string.format("Getting number of assets for target with TL=%d, Category=%s, nUnits=%s, MissionType=%s",threatlevel,targetcategory,nUnits,tostring(MissionType))) -local candidates={} -local threatlevelMatch=nil -for _,_assetnumber in pairs(self.assetNumbers or{})do -local assetnumber=_assetnumber -if(threatlevelMatch==nil and threatlevel>=assetnumber.threatlevel)or(threatlevelMatch~=nil and threatlevelMatch==threatlevel)then -if threatlevelMatch==nil then -threatlevelMatch=threatlevel -end -local nMatch=0 -local cand=true -if assetnumber.targetCategory~=nil then -if assetnumber.targetCategory==targetcategory then -nMatch=nMatch+1 -else -cand=false -end -end -if MissionType and assetnumber.missionType~=nil then -if assetnumber.missionType==MissionType then -nMatch=nMatch+1 -else -cand=false -end -end -if assetnumber.nUnits~=nil then -if assetnumber.nUnits>=nUnits then -nMatch=nMatch+1 -else -cand=false -end -end -if assetnumber.defcon~=nil then -if assetnumber.defcon==self.Defcon then -nMatch=nMatch+1 -else -cand=false -end -end -if assetnumber.strategy~=nil then -if assetnumber.strategy==self.strategy then -nMatch=nMatch+1 -else -cand=false -end -end -if cand then -table.insert(candidates,{assetnumber=assetnumber,nMatch=nMatch}) -end -end -end -if#candidates>0 then -local function _sort(a,b) -return a.nMatch>b.nMatch -end -table.sort(candidates,_sort) -local candidate=candidates[1] -local an=candidate.assetnumber -self:T(self.lid..string.format("Picking candidate with %d matches: NassetsMin=%d, NassetsMax=%d, ThreatLevel=%d, TargetCategory=%s, MissionType=%s, Defcon=%s, Strategy=%s", -candidate.nMatch,an.nAssetMin,an.nAssetMax,an.threatlevel,tostring(an.targetCategory),tostring(an.missionType),tostring(an.defcon),tostring(an.strategy))) -return an.nAssetMin,an.nAssetMax -else -return 1,1 -end -end -function CHIEF:GetDefcon(Defcon) -return self.Defcon -end -function CHIEF:SetLimitMission(Limit,MissionType) -self.commander:SetLimitMission(Limit,MissionType) -return self -end -function CHIEF:SetTacticalOverviewOn() -self.tacview=true -return self -end -function CHIEF:SetTacticalOverviewOff() -self.tacview=false -return self -end -function CHIEF:SetStrategy(Strategy) -if Strategy~=self.strategy then -self:StrategyChange(Strategy) -end -self.strategy=Strategy -return self -end -function CHIEF:GetDefcon(Defcon) -return self.Defcon -end -function CHIEF:GetCommander() -return self.commander -end -function CHIEF:AddAirwing(Airwing) -self:AddLegion(Airwing) -return self -end -function CHIEF:AddBrigade(Brigade) -self:AddLegion(Brigade) -return self -end -function CHIEF:AddFleet(Fleet) -self:AddLegion(Fleet) -return self -end -function CHIEF:AddLegion(Legion) -Legion.chief=self -self.commander:AddLegion(Legion) -return self -end -function CHIEF:RemoveLegion(Legion) -Legion.chief=nil -self.commander:RemoveLegion(Legion) -return self -end -function CHIEF:AddMission(Mission) -Mission.chief=self -Mission.statusChief=AUFTRAG.Status.PLANNED -self:I(self.lid..string.format("Adding mission #%d",Mission.auftragsnummer)) -self.commander:AddMission(Mission) -return self -end -function CHIEF:RemoveMission(Mission) -Mission.chief=nil -self.commander:RemoveMission(Mission) -return self -end -function CHIEF:AddOpsTransport(Transport) -Transport.chief=self -self.commander:AddOpsTransport(Transport) -return self -end -function CHIEF:RemoveTransport(Transport) -Transport.chief=nil -self.commander:RemoveTransport(Transport) -return self -end -function CHIEF:AddTarget(Target) -if not self:IsTarget(Target)then -Target.chief=self -table.insert(self.targetqueue,Target) -end -return self -end -function CHIEF:IsTarget(Target) -for _,_target in pairs(self.targetqueue)do -local target=_target -if target.uid==Target.uid or target:GetName()==Target:GetName()then -return true -end -end -return false -end -function CHIEF:RemoveTarget(Target) -for i,_target in pairs(self.targetqueue)do -local target=_target -if target.uid==Target.uid then -self:T(self.lid..string.format("Removing target %s from queue",Target.name)) -table.remove(self.targetqueue,i) -break -end -end -return self -end -function CHIEF:AddStrategicZone(OpsZone,Priority,Importance,ResourceOccupied,ResourceEmpty) -local stratzone={} -stratzone.opszone=OpsZone -stratzone.prio=Priority or 50 -stratzone.importance=Importance -stratzone.missions={} -if OpsZone:IsStopped()then -OpsZone:Start() -end -if ResourceOccupied then -stratzone.resourceOccup=UTILS.DeepCopy(ResourceOccupied) -else -stratzone.resourceOccup=self:CreateResource(AUFTRAG.Type.ARTY,1,2) -self:AddToResource(stratzone.resourceOccup,AUFTRAG.Type.CASENHANCED,1,2) -end -if ResourceEmpty then -stratzone.resourceEmpty=UTILS.DeepCopy(ResourceEmpty) -else -local resourceEmpty,resourceInfantry=self:CreateResource(AUFTRAG.Type.ONGUARD,1,3,GROUP.Attribute.GROUND_INFANTRY) -self:AddToResource(resourceEmpty,AUFTRAG.Type.ONGUARD,0,1,GROUP.Attribute.GROUND_TANK) -self:AddToResource(resourceEmpty,AUFTRAG.Type.ONGUARD,0,1,GROUP.Attribute.GROUND_IFV) -self:AddTransportToResource(resourceInfantry,0,1,{GROUP.Attribute.AIR_TRANSPORTHELO,GROUP.Attribute.GROUND_APC}) -stratzone.resourceEmpty=resourceEmpty -end -table.insert(self.zonequeue,stratzone) -OpsZone:_AddChief(self) -return stratzone -end -function CHIEF:SetStrategicZoneResourceEmpty(StrategicZone,Resource,NoCopy) -if NoCopy then -StrategicZone.resourceEmpty=Resource -else -StrategicZone.resourceEmpty=UTILS.DeepCopy(Resource) -end -return self -end -function CHIEF:SetStrategicZoneResourceOccupied(StrategicZone,Resource,NoCopy) -if NoCopy then -StrategicZone.resourceOccup=Resource -else -StrategicZone.resourceOccup=UTILS.DeepCopy(Resource) -end -return self -end -function CHIEF:GetStrategicZoneResourceEmpty(StrategicZone) -return StrategicZone.resourceEmpty -end -function CHIEF:GetStrategicZoneResourceOccupied(StrategicZone) -return StrategicZone.resourceOccup -end -function CHIEF:RemoveStrategicZone(OpsZone,Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,CHIEF.RemoveStrategicZone,self,OpsZone) -else -for i=#self.zonequeue,1,-1 do -local stratzone=self.zonequeue[i] -if OpsZone.zoneName==stratzone.opszone.zoneName then -self:T(self.lid..string.format("Removing OPS zone \"%s\" from queue! All running missions will be cancelled",OpsZone.zoneName)) -for _,_resource in pairs(stratzone.resourceEmpty)do -local resource=_resource -if resource.mission and resource.mission:IsNotOver()then -resource.mission:Cancel() -end -end -for _,_resource in pairs(stratzone.resourceOccup)do -local resource=_resource -if resource.mission and resource.mission:IsNotOver()then -resource.mission:Cancel() -end -end -table.remove(self.zonequeue,i) -return self -end -end -end -return self -end -function CHIEF:AddRearmingZone(RearmingZone) -local supplyzone=self.commander:AddRearmingZone(RearmingZone) -return supplyzone -end -function CHIEF:AddRefuellingZone(RefuellingZone) -local supplyzone=self.commander:AddRefuellingZone(RefuellingZone) -return supplyzone -end -function CHIEF:AddCapZone(Zone,Altitude,Speed,Heading,Leg) -local zone=self.commander:AddCapZone(Zone,Altitude,Speed,Heading,Leg) -return zone -end -function CHIEF:AddGciCapZone(Zone,Altitude,Speed,Heading,Leg) -local zone=self.commander:AddGciCapZone(Zone,Altitude,Speed,Heading,Leg) -return zone -end -function CHIEF:RemoveGciCapZone(Zone) -local zone=self.commander:RemoveGciCapZone(Zone) -return zone -end -function CHIEF:AddAwacsZone(Zone,Altitude,Speed,Heading,Leg) -local zone=self.commander:AddAwacsZone(Zone,Altitude,Speed,Heading,Leg) -return zone -end -function CHIEF:RemoveAwacsZone(Zone) -local zone=self.commander:RemoveAwacsZone(Zone) -return zone -end -function CHIEF:AddTankerZone(Zone,Altitude,Speed,Heading,Leg,RefuelSystem) -local zone=self.commander:AddTankerZone(Zone,Altitude,Speed,Heading,Leg,RefuelSystem) -return zone -end -function CHIEF:RemoveTankerZone(Zone) -local zone=self.commander:RemoveTankerZone(Zone) -return zone -end -function CHIEF:SetBorderZones(BorderZoneSet) -self.borderzoneset=BorderZoneSet or SET_ZONE:New() -return self -end -function CHIEF:AddBorderZone(Zone) -self.borderzoneset:AddZone(Zone) -return self -end -function CHIEF:RemoveBorderZone(Zone) -self.borderzoneset:Remove(Zone:GetName()) -return self -end -function CHIEF:SetConflictZones(ZoneSet) -self.yellowzoneset=ZoneSet or SET_ZONE:New() -return self -end -function CHIEF:AddConflictZone(Zone) -self.yellowzoneset:AddZone(Zone) -return self -end -function CHIEF:RemoveConflictZone(Zone) -self.yellowzoneset:Remove(Zone:GetName()) -return self -end -function CHIEF:SetAttackZones(ZoneSet) -self.engagezoneset=ZoneSet or SET_ZONE:New() -return self -end -function CHIEF:AddAttackZone(Zone) -self.engagezoneset:AddZone(Zone) -return self -end -function CHIEF:RemoveAttackZone(Zone) -self.engagezoneset:Remove(Zone:GetName()) -return self -end -function CHIEF:AllowGroundTransport() -env.warning("WARNING: CHIEF:AllowGroundTransport() is deprecated and will be removed in the future!") -self.TransportCategories={Group.Category.GROUND,Group.Category.HELICOPTER} -return self -end -function CHIEF:ForbidGroundTransport() -env.warning("WARNING: CHIEF:ForbidGroundTransport() is deprecated and will be removed in the future!") -self.TransportCategories={Group.Category.HELICOPTER} -return self -end -function CHIEF:IsPassive() -return self.strategy==CHIEF.Strategy.PASSIVE -end -function CHIEF:IsDefensive() -return self.strategy==CHIEF.Strategy.DEFENSIVE -end -function CHIEF:IsOffensive() -return self.strategy==CHIEF.Strategy.OFFENSIVE -end -function CHIEF:IsAgressive() -return self.strategy==CHIEF.Strategy.AGGRESSIVE -end -function CHIEF:IsTotalWar() -return self.strategy==CHIEF.Strategy.TOTALWAR -end -function CHIEF:onafterStart(From,Event,To) -local text=string.format("Starting Chief of Staff") -self:I(self.lid..text) -self:GetParent(self).onafterStart(self,From,Event,To) -if self.commander then -if self.commander:GetState()=="NotReadyYet"then -self.commander:Start() -end -end -end -function CHIEF:onafterStatus(From,Event,To) -self:GetParent(self).onafterStatus(self,From,Event,To) -local fsmstate=self:GetState() -for _,_contact in pairs(self.ContactsLost)do -local contact=_contact -if contact.mission and contact.mission:IsNotOver()then -local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.",contact.groupname,contact.mission.type:upper(),contact.mission.name) -MESSAGE:New(text,120,"CHIEF"):ToAll() -self:T(self.lid..text) -contact.mission:Cancel() -end -if contact.target then -self:RemoveTarget(contact.target) -end -end -self.Nborder=0;self.Nconflict=0;self.Nattack=0 -for _,_contact in pairs(self.Contacts)do -local contact=_contact -local group=contact.group -local inred=self:CheckGroupInBorder(group) -if inred then -self.Nborder=self.Nborder+1 -end -local inyellow=self:CheckGroupInConflict(group) -if inyellow then -self.Nconflict=self.Nconflict+1 -end -local inattack=self:CheckGroupInAttack(group) -if inattack then -self.Nattack=self.Nattack+1 -end -if not contact.target then -local Target=TARGET:New(contact.group) -contact.target=Target -Target.contact=contact -self:AddTarget(Target) -end -end -if self.Nborder>0 then -self:SetDefcon(CHIEF.DEFCON.RED) -elseif self.Nconflict>0 then -self:SetDefcon(CHIEF.DEFCON.YELLOW) -else -self:SetDefcon(CHIEF.DEFCON.GREEN) -end -self:CheckTargetQueue() -for _,_target in pairs(self.targetqueue)do -local target=_target -if target and target:IsAlive()and target.chief and target.mission and target.mission:IsNotOver()then -local inborder=self:CheckTargetInZones(target,self.borderzoneset) -local inyellow=self:CheckTargetInZones(target,self.yellowzoneset) -local inattack=self:CheckTargetInZones(target,self.engagezoneset) -if self.strategy==CHIEF.Strategy.PASSIVE then -self:T(self.lid..string.format("Cancelling mission for target %s as strategy is PASSIVE",target:GetName())) -target.mission:Cancel() -elseif self.strategy==CHIEF.Strategy.DEFENSIVE then -if not inborder then -self:T(self.lid..string.format("Cancelling mission for target %s as strategy is DEFENSIVE and not inside border",target:GetName())) -target.mission:Cancel() -end -elseif self.strategy==CHIEF.Strategy.OFFENSIVE then -if not(inborder or inyellow)then -self:T(self.lid..string.format("Cancelling mission for target %s as strategy is OFFENSIVE and not inside border or conflict",target:GetName())) -target.mission:Cancel() -end -elseif self.strategy==CHIEF.Strategy.AGGRESSIVE then -if not(inborder or inyellow or inattack)then -self:T(self.lid..string.format("Cancelling mission for target %s as strategy is AGGRESSIVE and not inside border, conflict or attack",target:GetName())) -target.mission:Cancel() -end -elseif self.strategy==CHIEF.Strategy.TOTALWAR then -end -end -end -self:CheckOpsZoneQueue() -self:_TacticalOverview() -if self.verbose>=1 then -local Nassets=self.commander:CountAssets() -local Ncontacts=#self.Contacts -local Nmissions=#self.commander.missionqueue -local Ntargets=#self.targetqueue -local text=string.format("Defcon=%s Strategy=%s: Assets=%d, Contacts=%d [Border=%d, Conflict=%d, Attack=%d], Targets=%d, Missions=%d", -self.Defcon,self.strategy,Nassets,Ncontacts,self.Nborder,self.Nconflict,self.Nattack,Ntargets,Nmissions) -self:I(self.lid..text) -end -if self.verbose>=2 and#self.Contacts>0 then -local text="Contacts:" -for i,_contact in pairs(self.Contacts)do -local contact=_contact -local mtext="N/A" -if contact.mission then -mtext=string.format("\"%s\" [%s] %s",contact.mission:GetName(),contact.mission:GetType(),contact.mission.status:upper()) -end -text=text..string.format("\n[%d] %s Type=%s (%s): Threat=%d Mission=%s",i,contact.groupname,contact.categoryname,contact.typename,contact.threatlevel,mtext) -end -self:I(self.lid..text) -end -if self.verbose>=3 and#self.targetqueue>0 then -local text="Targets:" -for i,_target in pairs(self.targetqueue)do -local target=_target -local mtext="N/A" -if target.mission then -mtext=string.format("\"%s\" [%s] %s",target.mission:GetName(),target.mission:GetType(),target.mission.status:upper()) -end -text=text..string.format("\n[%d] %s: Category=%s, prio=%d, importance=%d, alive=%s [%.1f/%.1f], Mission=%s", -i,target:GetName(),target.category,target.prio,target.importance or-1,tostring(target:IsAlive()),target:GetLife(),target:GetLife0(),mtext) -end -self:I(self.lid..text) -end -if self.verbose>=4 and#self.commander.missionqueue>0 then -local text="Mission queue:" -for i,_mission in pairs(self.commander.missionqueue)do -local mission=_mission -local target=mission:GetTargetName()or"unknown" -text=text..string.format("\n[%d] %s (%s): status=%s, target=%s",i,mission.name,mission.type,mission.status,target) -end -self:I(self.lid..text) -end -if self.verbose>=4 and#self.zonequeue>0 then -local text="Zone queue:" -for i,_stratzone in pairs(self.zonequeue)do -local stratzone=_stratzone -local opszone=stratzone.opszone -local owner=UTILS.GetCoalitionName(opszone.ownerCurrent) -local prevowner=UTILS.GetCoalitionName(opszone.ownerPrevious) -text=text..string.format("\n[%d] %s [%s]: owner=%s [%s] (prio=%d, importance=%s): Blue=%d, Red=%d, Neutral=%d", -i,opszone.zone:GetName(),opszone:GetState(),owner,prevowner,stratzone.prio,tostring(stratzone.importance),opszone.Nblu,opszone.Nred,opszone.Nnut) -end -self:I(self.lid..text) -end -if self.verbose>=5 then -local text="Assets:" -for _,missiontype in pairs(AUFTRAG.Type)do -local N=self.commander:CountAssets(nil,missiontype) -if N>0 then -text=text..string.format("\n- %s: %d",missiontype,N) -end -end -self:I(self.lid..text) -local text="Assets:" -for _,attribute in pairs(WAREHOUSE.Attribute)do -local N=self.commander:CountAssets(nil,nil,attribute) -if N>0 or self.verbose>=10 then -text=text..string.format("\n- %s: %d",attribute,N) -end -end -self:T(self.lid..text) -end -end -function CHIEF:onafterMissionAssign(From,Event,To,Mission,Legions) -if self.commander then -self:T(self.lid..string.format("Assigning mission %s (%s) to COMMANDER",Mission.name,Mission.type)) -Mission.chief=self -Mission.statusChief=AUFTRAG.Status.QUEUED -self.commander:MissionAssign(Mission,Legions) -else -self:E(self.lid..string.format("Mission cannot be assigned as no COMMANDER is defined!")) -end -end -function CHIEF:onafterMissionCancel(From,Event,To,Mission) -self:T(self.lid..string.format("Cancelling mission %s (%s) in status %s",Mission.name,Mission.type,Mission.status)) -Mission.statusChief=AUFTRAG.Status.CANCELLED -if Mission:IsPlanned()then -self:RemoveMission(Mission) -else -if Mission.commander then -Mission.commander:MissionCancel(Mission) -end -end -end -function CHIEF:onafterTransportCancel(From,Event,To,Transport) -self:T(self.lid..string.format("Cancelling transport UID=%d in status %s",Transport.uid,Transport:GetState())) -if Transport:IsPlanned()then -self:RemoveTransport(Transport) -else -if Transport.commander then -Transport.commander:TransportCancel(Transport) -end -end -end -function CHIEF:onafterDefconChange(From,Event,To,Defcon) -self:T(self.lid..string.format("Changing Defcon from %s --> %s",self.Defcon,Defcon)) -end -function CHIEF:onafterStrategyChange(From,Event,To,Strategy) -self:T(self.lid..string.format("Changing Strategy from %s --> %s",self.strategy,Strategy)) -end -function CHIEF:onafterOpsOnMission(From,Event,To,OpsGroup,Mission) -self:T(self.lid..string.format("Group %s on mission %s [%s]",OpsGroup:GetName(),Mission:GetName(),Mission:GetType())) -end -function CHIEF:onafterZoneCaptured(From,Event,To,OpsZone) -self:T(self.lid..string.format("Zone %s captured!",OpsZone:GetName())) -end -function CHIEF:onafterZoneLost(From,Event,To,OpsZone) -self:T(self.lid..string.format("Zone %s lost!",OpsZone:GetName())) -end -function CHIEF:onafterZoneEmpty(From,Event,To,OpsZone) -self:T(self.lid..string.format("Zone %s empty!",OpsZone:GetName())) -end -function CHIEF:onafterZoneAttacked(From,Event,To,OpsZone) -self:T(self.lid..string.format("Zone %s attacked!",OpsZone:GetName())) -end -function CHIEF:_TacticalOverview() -if self.tacview then -local NassetsTotal=self.commander:CountAssets() -local NassetsStock=self.commander:CountAssets(true) -local Ncontacts=#self.Contacts -local NmissionsTotal=#self.commander.missionqueue -local NmissionsRunni=self.commander:CountMissions(AUFTRAG.Type,true) -local Ntargets=#self.targetqueue -local Nzones=#self.zonequeue -local Nagents=self.detectionset:CountAlive() -local text=string.format("Tactical Overview\n") -text=text..string.format("=================\n") -text=text..string.format("Strategy: %s - Defcon: %s - Agents=%s\n",self.strategy,self.Defcon,Nagents) -text=text..string.format("Contacts: %d [Border=%d, Conflict=%d, Attack=%d]\n",Ncontacts,self.Nborder,self.Nconflict,self.Nattack) -text=text..string.format("Assets: %d [Active=%d, Stock=%d]\n",NassetsTotal,NassetsTotal-NassetsStock,NassetsStock) -text=text..string.format("Targets: %d\n",Ntargets) -text=text..string.format("Missions: %d [Running=%d/%d - Success=%d, Failure=%d]\n",NmissionsTotal,NmissionsRunni,self:GetMissionLimit("Total"),self.Nsuccess,self.Nfailure) -for _,mtype in pairs(AUFTRAG.Type)do -local n=self.commander:CountMissions(mtype) -if n>0 then -local N=self.commander:CountMissions(mtype,true) -local limit=self:GetMissionLimit(mtype) -text=text..string.format(" - %s: %d [Running=%d/%d]\n",mtype,n,N,limit) -end -end -text=text..string.format("Strategic Zones: %d\n",Nzones) -for _,_stratzone in pairs(self.zonequeue)do -local stratzone=_stratzone -local owner=stratzone.opszone:GetOwnerName() -text=text..string.format(" - %s: %s - %s [I=%d, P=%d]\n",stratzone.opszone:GetName(),owner,stratzone.opszone:GetState(),stratzone.importance or 0,stratzone.prio or 0) -end -local Ntransports=#self.commander.transportqueue -if Ntransports>0 then -text=text..string.format("Transports: %d\n",Ntransports) -for _,_transport in pairs(self.commander.transportqueue)do -local transport=_transport -text=text..string.format(" - %s",transport:GetState()) -end -end -MESSAGE:New(text,60,nil,true):ToCoalition(self.coalition) -if self.verbose>=4 then -self:I(self.lid..text) -end -end -end -function CHIEF:CheckTargetQueue() -local Ntargets=#self.targetqueue -if Ntargets==0 then -return nil -end -local NoLimit=self:_CheckMissionLimit("Total") -if NoLimit==false then -return nil -end -local function _sort(a,b) -local taskA=a -local taskB=b -return(taskA.priotaskB.threatlevel0) -end -table.sort(self.targetqueue,_sort) -local vip=math.huge -for _,_target in pairs(self.targetqueue)do -local target=_target -if target:IsAlive()and target.importance and target.importance=self.threatLevelMin and threatlevel<=self.threatLevelMax -if target.category==TARGET.Category.AIRBASE or target.category==TARGET.Category.ZONE or target.Category==TARGET.Category.COORDINATE then -isThreat=true -end -local text=string.format("Target %s: Alive=%s, Threat=%s, Important=%s",target:GetName(),tostring(isAlive),tostring(isThreat),tostring(isImportant)) -if target.mission then -text=text..string.format(", Mission \"%s\" (%s) [%s]",target.mission:GetName(),target.mission:GetState(),target.mission:GetType()) -if target.mission:IsOver()then -text=text..string.format(" - DONE ==> removing mission") -target.mission=nil -end -else -text=text..string.format(", NO mission yet") -end -self:T2(self.lid..text) -if isAlive and isThreat and isImportant and not target.mission then -local valid=false -if self.strategy==CHIEF.Strategy.PASSIVE then -valid=false -elseif self.strategy==CHIEF.Strategy.DEFENSIVE then -if self:CheckTargetInZones(target,self.borderzoneset)then -valid=true -end -elseif self.strategy==CHIEF.Strategy.OFFENSIVE then -if self:CheckTargetInZones(target,self.borderzoneset)or self:CheckTargetInZones(target,self.yellowzoneset)then -valid=true -end -elseif self.strategy==CHIEF.Strategy.AGGRESSIVE then -if self:CheckTargetInZones(target,self.borderzoneset)or self:CheckTargetInZones(target,self.yellowzoneset)or self:CheckTargetInZones(target,self.engagezoneset)then -valid=true -end -elseif self.strategy==CHIEF.Strategy.TOTALWAR then -valid=true -end -if valid then -self:T(self.lid..string.format("Got valid target %s: category=%s, threatlevel=%d",target:GetName(),target.category,threatlevel)) -local MissionPerformances=self:_GetMissionPerformanceFromTarget(target) -local mission=nil -local Legions=nil -if#MissionPerformances>0 then -for _,_mp in pairs(MissionPerformances)do -local mp=_mp -local notlimited=self:_CheckMissionLimit(mp.MissionType) -if notlimited then -local NassetsMin,NassetsMax=self:_GetAssetsForTarget(target,mp.MissionType) -self:T2(self.lid..string.format("Recruiting assets for mission type %s [performance=%d] of target %s",mp.MissionType,mp.Performance,target:GetName())) -local recruited,assets,legions=self.commander:RecruitAssetsForTarget(target,mp.MissionType,NassetsMin,NassetsMax) -if recruited then -self:T(self.lid..string.format("Recruited %d assets for mission type %s [performance=%d] of target %s",#assets,mp.MissionType,mp.Performance,target:GetName())) -mission=AUFTRAG:NewFromTarget(target,mp.MissionType) -if mission then -mission:_AddAssets(assets) -Legions=legions -break -end -else -self:T(self.lid..string.format("Could NOT recruit assets for mission type %s [performance=%d] of target %s",mp.MissionType,mp.Performance,target:GetName())) -end -end -end -end -if mission and Legions then -target.mission=mission -mission.prio=target.prio -mission.importance=target.importance -self:MissionAssign(mission,Legions) -return -end -end -end -end -end -function CHIEF:_CheckMissionLimit(MissionType) -return self.commander:_CheckMissionLimit(MissionType) -end -function CHIEF:GetMissionLimit(MissionType) -local l=self.commander.limitMission[MissionType] -if not l then -l=999 -end -return l -end -function CHIEF:CheckOpsZoneQueue() -local Nzones=#self.zonequeue -if Nzones==0 then -return nil -end -for i=Nzones,1,-1 do -local stratzone=self.zonequeue[i] -if stratzone.opszone:IsStopped()then -self:RemoveStrategicZone(stratzone.opszone) -end -end -for _,_startzone in pairs(self.zonequeue)do -local stratzone=_startzone -local ownercoalition=stratzone.opszone:GetOwner() -if ownercoalition==self.coalition or stratzone.opszone:IsEmpty()then -for _,_resource in pairs(stratzone.resourceOccup or{})do -local resource=_resource -if resource.mission then -resource.mission:Cancel() -end -end -end -end -if self:IsPassive()then -return -end -local NoLimit=self:_CheckMissionLimit("Total") -if NoLimit==false then -return nil -end -local function _sort(a,b) -local taskA=a -local taskB=b -return(taskA.prio Recruiting for mission type %s: Nmin=%d, Nmax=%d",zoneName,missionType,resource.Nmin,resource.Nmax)) -local recruited=self:RecruitAssetsForZone(stratzone,resource) -if recruited then -self:T(self.lid..string.format("Successfully recruited assets for empty zone \"%s\" [mission type=%s]",zoneName,missionType)) -else -self:T(self.lid..string.format("Could not recruited assets for empty zone \"%s\" [mission type=%s]",zoneName,missionType)) -end -end -end -else -for _,_resource in pairs(stratzone.resourceOccup or{})do -local resource=_resource -local missionType=resource.MissionType -if(not resource.mission)or resource.mission:IsOver()then -self:T2(self.lid..string.format("Zone %s is NOT empty ==> Recruiting for mission type %s: Nmin=%d, Nmax=%d",zoneName,missionType,resource.Nmin,resource.Nmax)) -local recruited=self:RecruitAssetsForZone(stratzone,resource) -if recruited then -self:T(self.lid..string.format("Successfully recruited assets for occupied zone %s, mission type=%s",zoneName,missionType)) -else -self:T(self.lid..string.format("Could not recruited assets for occupied zone %s, mission type=%s",zoneName,missionType)) -end -end -end -end -end -end -end -function CHIEF:CheckGroupInBorder(group) -local inside=self:CheckGroupInZones(group,self.borderzoneset) -return inside -end -function CHIEF:CheckGroupInConflict(group) -local inside=self:CheckGroupInZones(group,self.yellowzoneset) -return inside -end -function CHIEF:CheckGroupInAttack(group) -local inside=self:CheckGroupInZones(group,self.engagezoneset) -return inside -end -function CHIEF:CheckGroupInZones(group,zoneset) -for _,_zone in pairs(zoneset.Set or{})do -local zone=_zone -if group:IsInZone(zone)then -return true -end -end -return false -end -function CHIEF:CheckTargetInZones(target,zoneset) -for _,_zone in pairs(zoneset.Set or{})do -local zone=_zone -if zone:IsCoordinateInZone(target:GetCoordinate())then -return true -end -end -return false -end -function CHIEF:_CreateMissionPerformance(MissionType,Performance) -local mp={} -mp.MissionType=MissionType -mp.Performance=Performance -return mp -end -function CHIEF:_GetMissionPerformanceFromTarget(Target) -local group=nil -local airbase=nil -local scenery=nil -local static=nil -local coordinate=nil -local target=Target:GetObject() -if target:IsInstanceOf("GROUP")then -group=target -elseif target:IsInstanceOf("UNIT")then -group=target:GetGroup() -elseif target:IsInstanceOf("AIRBASE")then -airbase=target -elseif target:IsInstanceOf("STATIC")then -static=target -elseif target:IsInstanceOf("SCENERY")then -scenery=target -end -local TargetCategory=Target:GetCategory() -local missionperf={} -if group then -local category=group:GetCategory() -local attribute=group:GetAttribute() -if category==Group.Category.AIRPLANE or category==Group.Category.HELICOPTER then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.INTERCEPT,100)) -elseif category==Group.Category.GROUND or category==Group.Category.TRAIN then -if attribute==GROUP.Attribute.GROUND_SAM then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.SEAD,100)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) -elseif attribute==GROUP.Attribute.GROUND_EWR then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) -elseif attribute==GROUP.Attribute.GROUND_AAA then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK,40)) -elseif attribute==GROUP.Attribute.GROUND_ARTILLERY then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,75)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK,70)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING,70)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) -elseif attribute==GROUP.Attribute.GROUND_INFANTRY then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK,40)) -elseif attribute==GROUP.Attribute.GROUND_TANK then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.CAS,90)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.CASENHANCED,90)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK,40)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) -else -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) -end -elseif category==Group.Category.SHIP then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ANTISHIP,100)) -else -self:E(self.lid.."ERROR: Unknown Group category!") -end -elseif airbase then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBRUNWAY,100)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) -elseif static then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING,70)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET,50)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) -elseif scenery then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.STRIKE,100)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING,70)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET,50)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) -elseif coordinate then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING,100)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET,50)) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) -end -return missionperf -end -function CHIEF:_GetMissionTypeForGroupAttribute(Attribute) -local missionperf={} -if Attribute==GROUP.Attribute.AIR_ATTACKHELO then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.INTERCEPT),100) -elseif Attribute==GROUP.Attribute.GROUND_AAA then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI),100) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING),80) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET),70) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY),30) -elseif Attribute==GROUP.Attribute.GROUND_SAM then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.SEAD),100) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI),90) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY),50) -elseif Attribute==GROUP.Attribute.GROUND_EWR then -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.SEAD),100) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI),100) -table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY),50) -end -return missionperf -end -function CHIEF:RecruitAssetsForZone(StratZone,Resource) -local Cohorts=self.commander:_GetCohorts() -local MissionType=Resource.MissionType -local NassetsMin=Resource.Nmin -local NassetsMax=Resource.Nmax -local Categories=Resource.Categories -local Attributes=Resource.Attributes -local Properties=Resource.Properties -local TargetVec2=StratZone.opszone.zone:GetVec2() -local RangeMax=nil -if MissionType==AUFTRAG.Type.PATROLZONE or MissionType==AUFTRAG.Type.ONGUARD then -RangeMax=UTILS.NMToMeters(250) -end -if MissionType==AUFTRAG.Type.ARMOREDGUARD then -RangeMax=UTILS.NMToMeters(50) -end -self:T(self.lid.."Missiontype="..MissionType) -self:T({categories=Categories}) -self:T({attributes=Attributes}) -self:T({properties=Properties}) -local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,MissionType,nil,NassetsMin,NassetsMax,TargetVec2,nil,RangeMax,nil,nil,nil,nil,Categories,Attributes,Properties) -if recruited then -local mission=nil -self:T2(self.lid..string.format("Recruited %d assets for %s mission STRATEGIC zone %s",#assets,MissionType,tostring(StratZone.opszone.zoneName))) -local TargetZone=StratZone.opszone.zone -local TargetCoord=TargetZone:GetCoordinate() -local transport=nil -if Resource.carrierNmin and Resource.carrierNmax and Resource.carrierNmax>0 then -local cargoassets=CHIEF._FilterAssets(assets,Resource.Categories,Resource.Attributes,Resource.Properties) -if#cargoassets>0 then -recruited,transport=LEGION.AssignAssetsForTransport(self.commander,self.commander.legions,cargoassets, -Resource.carrierNmin,Resource.carrierNmax,TargetZone,nil,Resource.carrierCategories,Resource.carrierAttributes,Resource.carrierProperties) -end -end -if not recruited then -self:T(self.lid..string.format("Could not allocate assets or transport of OPSZONE!")) -LEGION.UnRecruitAssets(assets) -return false -end -self:T2(self.lid..string.format("Recruited %d assets for mission %s",#assets,MissionType)) -if MissionType==AUFTRAG.Type.PATROLZONE or MissionType==AUFTRAG.Type.ONGUARD then -if MissionType==AUFTRAG.Type.PATROLZONE then -mission=AUFTRAG:NewPATROLZONE(TargetZone) -elseif MissionType==AUFTRAG.Type.ONGUARD then -mission=AUFTRAG:NewONGUARD(TargetZone:GetRandomCoordinate(nil,nil,{land.SurfaceType.LAND})) -end -mission:SetEngageDetected(25,{"Ground Units","Light armed ships","Helicopters"}) -elseif MissionType==AUFTRAG.Type.CAPTUREZONE then -mission=AUFTRAG:NewCAPTUREZONE(StratZone.opszone,self.coalition) -elseif MissionType==AUFTRAG.Type.CASENHANCED then -local height=UTILS.MetersToFeet(TargetCoord:GetLandHeight())+2500 -local Speed=200 -if assets[1]then -if assets[1].speedmax then -Speed=UTILS.KmphToKnots(assets[1].speedmax*0.7)or 200 -end -end -mission=AUFTRAG:NewCASENHANCED(TargetZone,height,Speed) -elseif MissionType==AUFTRAG.Type.CAS then -local height=UTILS.MetersToFeet(TargetCoord:GetLandHeight())+2500 -local Speed=200 -if assets[1]then -if assets[1].speedmax then -Speed=UTILS.KmphToKnots(assets[1].speedmax*0.7)or 200 -end -end -TargetZone=StratZone.opszone.zoneCircular -local Leg=TargetZone:GetRadius()<=10000 and 5 or UTILS.MetersToNM(TargetZone:GetRadius()) -mission=AUFTRAG:NewCAS(TargetZone,height,Speed,TargetCoord,math.random(0,359),Leg) -elseif MissionType==AUFTRAG.Type.ARTY then -local Radius=TargetZone:GetRadius() -mission=AUFTRAG:NewARTY(TargetCoord,120,Radius) -elseif MissionType==AUFTRAG.Type.ARMOREDGUARD then -mission=AUFTRAG:NewARMOREDGUARD(TargetCoord) -elseif MissionType==AUFTRAG.Type.BOMBCARPET then -mission=AUFTRAG:NewBOMBCARPET(TargetCoord,nil,1000) -elseif MissionType==AUFTRAG.Type.BOMBING then -local coord=TargetZone:GetRandomCoordinate() -mission=AUFTRAG:NewBOMBING(TargetCoord) -elseif MissionType==AUFTRAG.Type.RECON then -mission=AUFTRAG:NewRECON(TargetZone,nil,5000) -elseif MissionType==AUFTRAG.Type.BARRAGE then -mission=AUFTRAG:NewBARRAGE(TargetZone) -elseif MissionType==AUFTRAG.Type.AMMOSUPPLY then -mission=AUFTRAG:NewAMMOSUPPLY(TargetZone) -end -if mission then -mission:_AddAssets(assets) -self:MissionAssign(mission,legions) -StratZone.opszone:_AddMission(self.coalition,MissionType,mission) -Resource.mission=mission -if transport then -mission.opstransport=transport -transport.opszone=StratZone.opszone -transport.chief=self -transport.commander=self.commander -end -return true -else -self:E(self.lid..string.format("ERROR: Mission type not supported for OPSZONE! Unrecruiting assets...")) -LEGION.UnRecruitAssets(assets) -return false -end -end -self:T2(self.lid..string.format("Could NOT recruit assets for %s mission of STRATEGIC zone %s",MissionType,tostring(StratZone.opszone.zoneName))) -return false -end -function CHIEF._FilterAssets(Assets,Categories,Attributes,Properties) -local filtered={} -for _,_asset in pairs(Assets)do -local asset=_asset -local hasCat=CHIEF._CheckAssetCategories(asset,Categories) -local hasAtt=CHIEF._CheckAssetAttributes(asset,Attributes) -local hasPro=CHIEF._CheckAssetProperties(asset,Properties) -if hasAtt and hasCat and hasPro then -table.insert(filtered,asset) -end -end -return filtered -end -function CHIEF._CheckAssetAttributes(Asset,Attributes) -if not Attributes then -return true -end -for _,attribute in pairs(UTILS.EnsureTable(Attributes))do -if attribute==Asset.attribute then -return true -end -end -return false -end -function CHIEF._CheckAssetCategories(Asset,Categories) -if not Categories then -return true -end -for _,attribute in pairs(UTILS.EnsureTable(Categories))do -if attribute==Asset.category then -return true -end -end -return false -end -function CHIEF._CheckAssetProperties(Asset,Properties) -if not Properties then -return true -end -for _,attribute in pairs(UTILS.EnsureTable(Properties))do -if attribute==Asset.DCSdesc then -return true -end -end -return false -end -COHORT={ -ClassName="COHORT", -verbose=0, -lid=nil, -name=nil, -templatename=nil, -assets={}, -missiontypes={}, -repairtime=0, -maintenancetime=0, -livery=nil, -skill=nil, -legion=nil, -Ngroups=0, -engageRange=nil, -tacanChannel={}, -weightAsset=99999, -cargobayLimit=0, -descriptors={}, -properties={}, -operations={}, -} -COHORT.version="0.3.5" -function COHORT:New(TemplateGroupName,Ngroups,CohortName) -local self=BASE:Inherit(self,FSM:New()) -self.templatename=TemplateGroupName -self.name=tostring(CohortName or TemplateGroupName) -self.lid=string.format("COHORT %s | ",self.name) -self.templategroup=GROUP:FindByName(self.templatename) -if not self.templategroup then -self:E(self.lid..string.format("ERROR: Template group %s does not exist!",tostring(self.templatename))) -return nil -end -self.attribute=self.templategroup:GetAttribute() -self.category=self.templategroup:GetCategory() -self.aircrafttype=self.templategroup:GetTypeName() -self.descriptors=self.templategroup:GetUnit(1):GetDesc() -self.properties=self.descriptors.attributes -self.Ngroups=Ngroups or 3 -self:SetSkill(AI.Skill.GOOD) -if self.category==Group.Category.AIRPLANE then -self:SetMissionRange(200) -elseif self.category==Group.Category.HELICOPTER then -self:SetMissionRange(150) -elseif self.category==Group.Category.GROUND then -self:SetMissionRange(75) -elseif self.category==Group.Category.SHIP then -self:SetMissionRange(100) -elseif self.category==Group.Category.TRAIN then -self:SetMissionRange(100) -else -self:SetMissionRange(150) -end -local units=self.templategroup:GetUnits() -self.weightAsset=0 -for i,_unit in pairs(units)do -local unit=_unit -local desc=unit:GetDesc() -local mass=666 -if desc then -mass=desc.massMax or desc.massEmpty -end -self.weightAsset=self.weightAsset+(mass or 666) -if i==1 then -self.cargobayLimit=unit:GetCargoBayFreeWeight() -end -end -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","OnDuty") -self:AddTransition("*","Status","*") -self:AddTransition("OnDuty","Pause","Paused") -self:AddTransition("Paused","Unpause","OnDuty") -self:AddTransition("OnDuty","Relocate","Relocating") -self:AddTransition("Relocating","Relocated","OnDuty") -self:AddTransition("*","Stop","Stopped") -return self -end -function COHORT:SetLivery(LiveryName) -self.livery=LiveryName -return self -end -function COHORT:SetSkill(Skill) -self.skill=Skill -return self -end -function COHORT:SetVerbosity(VerbosityLevel) -self.verbose=VerbosityLevel or 0 -return self -end -function COHORT:SetTurnoverTime(MaintenanceTime,RepairTime) -self.maintenancetime=MaintenanceTime and MaintenanceTime*60 or 0 -self.repairtime=RepairTime and RepairTime*60 or 0 -return self -end -function COHORT:SetRadio(Frequency,Modulation) -self.radioFreq=Frequency or 251 -self.radioModu=Modulation or radio.modulation.AM -return self -end -function COHORT:SetGrouping(nunits) -self.ngrouping=nunits or 2 -return self -end -function COHORT:AddMissionCapability(MissionTypes,Performance) -if MissionTypes and type(MissionTypes)~="table"then -MissionTypes={MissionTypes} -end -self.missiontypes=self.missiontypes or{} -for _,missiontype in pairs(MissionTypes)do -local Capability=self:GetMissionCapability(missiontype) -if Capability then -self:E(self.lid.."WARNING: Mission capability already present! No need to add it twice. Will update the performance though!") -Capability.Performance=Performance or 50 -else -local capability={} -capability.MissionType=missiontype -capability.Performance=Performance or 50 -table.insert(self.missiontypes,capability) -self:T(self.lid..string.format("Adding mission capability %s, performance=%d",tostring(capability.MissionType),capability.Performance)) -end -end -self:T2(self.missiontypes) -return self -end -function COHORT:GetMissionCapability(MissionType) -for _,_capability in pairs(self.missiontypes)do -local capability=_capability -if capability.MissionType==MissionType then -return capability -end -end -return nil -end -function COHORT:HasProperty(Property) -for _,property in pairs(self.properties)do -if Property==property then -return true -end -end -return false -end -function COHORT:GetMissionTypes() -local missiontypes={} -for _,Capability in pairs(self.missiontypes)do -local capability=Capability -table.insert(missiontypes,capability.MissionType) -end -return missiontypes -end -function COHORT:GetMissionCapabilities() -return self.missiontypes -end -function COHORT:GetMissionPeformance(MissionType) -for _,Capability in pairs(self.missiontypes)do -local capability=Capability -if capability.MissionType==MissionType then -return capability.Performance -end -end -return-1 -end -function COHORT:SetMissionRange(Range) -self.engageRange=UTILS.NMToMeters(Range or 150) -return self -end -function COHORT:SetCallsign(Callsign,Index) -self.callsignName=Callsign -self.callsignIndex=Index -return self -end -function COHORT:SetAttribute(Attribute) -self.attribute=Attribute -return self -end -function COHORT:GetAttribute() -return self.attribute -end -function COHORT:GetCategory() -return self.category -end -function COHORT:GetProperties() -return self.properties -end -function COHORT:SetModex(Modex,Prefix,Suffix) -self.modex=Modex -self.modexPrefix=Prefix -self.modexSuffix=Suffix -return self -end -function COHORT:SetLegion(Legion) -self.legion=Legion -return self -end -function COHORT:AddAsset(Asset) -self:T(self.lid..string.format("Adding asset %s of type %s",Asset.spawngroupname,Asset.unittype)) -Asset.squadname=self.name -Asset.legion=self.legion -Asset.cohort=self -table.insert(self.assets,Asset) -return self -end -function COHORT:DelAsset(Asset) -for i,_asset in pairs(self.assets)do -local asset=_asset -if Asset.uid==asset.uid then -self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) -table.remove(self.assets,i) -break -end -end -return self -end -function COHORT:DelGroup(GroupName) -for i,_asset in pairs(self.assets)do -local asset=_asset -if GroupName==asset.spawngroupname then -self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) -table.remove(self.assets,i) -break -end -end -return self -end -function COHORT:GetName() -return self.name -end -function COHORT:GetRadio() -return self.radioFreq,self.radioModu -end -function COHORT:GetCallsign(Asset) -if self.callsignName then -Asset.callsign={} -for i=1,Asset.nunits do -local callsign={} -callsign[1]=self.callsignName -callsign[2]=math.floor(self.callsigncounter/10) -callsign[3]=self.callsigncounter%10 -if callsign[3]==0 then -callsign[3]=1 -self.callsigncounter=self.callsigncounter+2 -else -self.callsigncounter=self.callsigncounter+1 -end -Asset.callsign[i]=callsign -self:T3({callsign=callsign}) -end -end -end -function COHORT:GetModex(Asset) -if self.modex then -Asset.modex={} -for i=1,Asset.nunits do -Asset.modex[i]=string.format("%03d",self.modex+self.modexcounter) -self.modexcounter=self.modexcounter+1 -self:T3({modex=Asset.modex[i]}) -end -end -end -function COHORT:AddTacanChannel(ChannelMin,ChannelMax) -ChannelMax=ChannelMax or ChannelMin -if ChannelMin>126 then -self:E(self.lid.."ERROR: TACAN Channel must be <= 126! Will not add to available channels") -return self -end -if ChannelMax>126 then -self:E(self.lid.."WARNING: TACAN Channel must be <= 126! Adjusting ChannelMax to 126") -ChannelMax=126 -end -for i=ChannelMin,ChannelMax do -self.tacanChannel[i]=true -end -return self -end -function COHORT:FetchTacan() -local freechannel=nil -for channel,free in pairs(self.tacanChannel)do -if free then -if freechannel==nil or channel=2 then -local text="Weapon data:" -for _,_weapondata in pairs(self.weaponData)do -local weapondata=_weapondata -text=text..string.format("\n- Bit=%s, Rmin=%d m, Rmax=%d m",tostring(weapondata.BitType),weapondata.RangeMin,weapondata.RangeMax) -end -self:I(self.lid..text) -end -return self -end -function COHORT:GetWeaponData(BitType) -return self.weaponData[tostring(BitType)] -end -function COHORT:IsOnDuty() -return self:Is("OnDuty") -end -function COHORT:IsStopped() -return self:Is("Stopped") -end -function COHORT:IsPaused() -return self:Is("Paused") -end -function COHORT:IsRelocating() -return self:Is("Relocating") -end -function COHORT:onafterStart(From,Event,To) -local text=string.format("Starting %s v%s %s [%s]",self.ClassName,self.version,self.name,self.attribute) -self:I(self.lid..text) -self:__Status(-1) -end -function COHORT:_CheckAssetStatus() -if self.verbose>=2 and#self.assets>0 then -local text="" -for j,_asset in pairs(self.assets)do -local asset=_asset -text=text..string.format("\n[%d] %s (%s*%d): ",j,asset.spawngroupname,asset.unittype,asset.nunits) -if asset.spawned then -local mission=self.legion and self.legion:GetAssetCurrentMission(asset)or false -if mission then -local distance=asset.flightgroup and UTILS.MetersToNM(mission:GetTargetDistance(asset.flightgroup.group:GetCoordinate()))or 0 -text=text..string.format("Mission %s - %s: Status=%s, Dist=%.1f NM",mission.name,mission.type,mission.status,distance) -else -text=text.."Mission None" -end -text=text..", Flight: " -if asset.flightgroup and asset.flightgroup:IsAlive()then -local status=asset.flightgroup:GetState() -text=text..string.format("%s",status) -if asset.flightgroup:IsFlightgroup()then -local fuelmin=asset.flightgroup:GetFuelMin() -local fuellow=asset.flightgroup:IsFuelLow() -local fuelcri=asset.flightgroup:IsFuelCritical() -text=text..string.format("Fuel=%d",fuelmin) -if fuelcri then -text=text.." (Critical!)" -elseif fuellow then -text=text.." (Low)" -end -end -local lifept,lifept0=asset.flightgroup:GetLifePoints() -text=text..string.format(", Life=%d/%d",lifept,lifept0) -local ammo=asset.flightgroup:GetAmmoTot() -text=text..string.format(", Ammo=%d [G=%d, R=%d, B=%d, M=%d]",ammo.Total,ammo.Guns,ammo.Rockets,ammo.Bombs,ammo.Missiles) -else -text=text.."N/A" -end -if asset.flightgroup:IsFlightgroup()then -local payload=asset.payload and table.concat(self.legion:GetPayloadMissionTypes(asset.payload),", ")or"None" -text=text..", Payload={"..payload.."}" -end -else -text=text..string.format("In Stock") -if self:IsRepaired(asset)then -text=text..", Combat Ready" -else -text=text..string.format(", Repaired in %d sec",self:GetRepairTime(asset)) -if asset.damage then -text=text..string.format(" (Damage=%.1f)",asset.damage) -end -end -if asset.Treturned then -local T=timer.getAbsTime()-asset.Treturned -text=text..string.format(", Returned for %d sec",T) -end -end -end -self:T(self.lid..text) -end -end -function COHORT:onafterStop(From,Event,To) -self:T(self.lid.."STOPPING Cohort and removing all assets!") -for i=#self.assets,1,-1 do -local asset=self.assets[i] -self:DelAsset(asset) -end -self.CallScheduler:Clear() -end -function COHORT:CanMission(Mission) -local cando=true -if not self:IsOnDuty()then -self:T(self.lid..string.format("Cohort in not OnDuty but in state %s. Cannot do mission %s with target %s",self:GetState(),Mission.name,Mission:GetTargetName())) -return false -end -if not AUFTRAG.CheckMissionType(Mission.type,self:GetMissionTypes())then -self:T(self.lid..string.format("INFO: Cohort cannot do mission type %s (%s, %s)",Mission.type,Mission.name,Mission:GetTargetName())) -return false -end -if Mission.type==AUFTRAG.Type.TANKER then -if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then -else -self:T(self.lid..string.format("INFO: Wrong refueling system requested=%s != %s=available",tostring(Mission.refuelSystem),tostring(self.tankerSystem))) -return false -end -end -local TargetDistance=Mission:GetTargetDistance(self.legion:GetCoordinate()) -local engagerange=Mission.engageRange and math.max(self.engageRange,Mission.engageRange)or self.engageRange -if TargetDistance>engagerange then -self:T(self.lid..string.format("INFO: Cohort is not in range. Target dist=%d > %d NM max mission Range",UTILS.MetersToNM(TargetDistance),UTILS.MetersToNM(engagerange))) -return false -end -return true -end -function COHORT:CountAssets(InStock,MissionTypes,Attributes) -local N=0 -for _,_asset in pairs(self.assets)do -local asset=_asset -if MissionTypes==nil or AUFTRAG.CheckMissionCapability(MissionTypes,self.missiontypes)then -if Attributes==nil or self:CheckAttribute(Attributes)then -if asset.spawned then -if InStock==false or InStock==nil then -N=N+1 -end -else -if InStock==true or InStock==nil then -N=N+1 -end -end -end -end -end -return N -end -function COHORT:GetOpsGroups(MissionTypes,Attributes) -local set=SET_OPSGROUP:New() -for _,_asset in pairs(self.assets)do -local asset=_asset -if MissionTypes==nil or AUFTRAG.CheckMissionCapability(MissionTypes,self.missiontypes)then -if Attributes==nil or self:CheckAttribute(Attributes)then -if asset.flightgroup and asset.flightgroup:IsAlive()then -set:AddGroup(asset.flightgroup) -end -end -end -end -return set -end -function COHORT:RecruitAssets(MissionType,Npayloads) -self:T2(self.lid..string.format("Recruiting asset for Mission type=%s",MissionType)) -local assets={} -for _,_asset in pairs(self.assets)do -local asset=_asset -local isRequested=asset.requested -local isReserved=asset.isReserved -local isSpawned=asset.spawned -local isOnMission=self.legion:IsAssetOnMission(asset) -local opsgroup=asset.flightgroup -self:T(self.lid..string.format("Asset %s: requested=%s, reserved=%s, spawned=%s, onmission=%s", -asset.spawngroupname,tostring(isRequested),tostring(isReserved),tostring(isSpawned),tostring(isOnMission))) -if not(isRequested or isReserved)then -if self.legion:IsAssetOnMission(asset)then -if MissionType==AUFTRAG.Type.RELOCATECOHORT then -table.insert(assets,asset) -elseif self.legion:IsAssetOnMission(asset,AUFTRAG.Type.NOTHING)then -table.insert(assets,asset) -elseif self.legion:IsAssetOnMission(asset,{AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK})and MissionType==AUFTRAG.Type.INTERCEPT then -self:T(self.lid..string.format("Adding asset on GCICAP mission for an INTERCEPT mission")) -table.insert(assets,asset) -elseif self.legion:IsAssetOnMission(asset,AUFTRAG.Type.ONGUARD)and(MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK)then -if not opsgroup:IsOutOfAmmo()then -self:T(self.lid..string.format("Adding asset on ONGUARD mission for an XXX mission")) -table.insert(assets,asset) -end -elseif self.legion:IsAssetOnMission(asset,AUFTRAG.Type.PATROLZONE)and(MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK)then -if not opsgroup:IsOutOfAmmo()then -self:T(self.lid..string.format("Adding asset on PATROLZONE mission for an XXX mission")) -table.insert(assets,asset) -end -elseif self.legion:IsAssetOnMission(asset,AUFTRAG.Type.ALERT5)and AUFTRAG.CheckMissionCapability(MissionType,asset.payload.capabilities)and MissionType~=AUFTRAG.Type.ALERT5 then -self:T(self.lid..string.format("Adding asset on ALERT 5 mission for %s mission",MissionType)) -table.insert(assets,asset) -end -else -if asset.spawned then -local flightgroup=asset.flightgroup -if flightgroup and flightgroup:IsAlive()and not(flightgroup:IsDead()or flightgroup:IsStopped())then -local combatready=true -if flightgroup:IsFlightgroup()then -if flightgroup:IsFuelLow()then -combatready=false -end -if MissionType==AUFTRAG.Type.INTERCEPT and not flightgroup:CanAirToAir()then -combatready=false -else -local excludeguns=MissionType==AUFTRAG.Type.BOMBING or MissionType==AUFTRAG.Type.BOMBRUNWAY or MissionType==AUFTRAG.Type.BOMBCARPET or MissionType==AUFTRAG.Type.SEAD or MissionType==AUFTRAG.Type.ANTISHIP -if excludeguns and not flightgroup:CanAirToGround(excludeguns)then -combatready=false -end -end -if flightgroup:IsHolding()or flightgroup:IsLanding()or flightgroup:IsLanded()or flightgroup:IsArrived()then -combatready=false -end -if asset.payload and not AUFTRAG.CheckMissionCapability(MissionType,asset.payload.capabilities)then -combatready=false -end -else -if flightgroup:IsArmygroup()then -if asset.attribute==WAREHOUSE.Attribute.GROUND_ARTILLERY or -asset.attribute==WAREHOUSE.Attribute.GROUND_TANK or -asset.attribute==WAREHOUSE.Attribute.GROUND_INFANTRY or -asset.attribute==WAREHOUSE.Attribute.GROUND_AAA or -asset.attribute==WAREHOUSE.Attribute.GROUND_SAM -then -combatready=true -end -else -combatready=false -end -if flightgroup:IsRearming()or flightgroup:IsRetreating()or flightgroup:IsReturning()then -combatready=false -end -end -if flightgroup:IsLoading()or flightgroup:IsTransporting()or flightgroup:IsUnloading()or flightgroup:IsPickingup()or flightgroup:IsCarrier()then -combatready=false -end -if flightgroup:IsCargo()or flightgroup:IsBoarding()or flightgroup:IsAwaitingLift()then -combatready=false -end -if combatready then -self:T(self.lid.."Adding SPAWNED asset to ANOTHER mission as it is COMBATREADY") -table.insert(assets,asset) -end -end -else -if Npayloads>0 and self:IsRepaired(asset)then -table.insert(assets,asset) -Npayloads=Npayloads-1 -end -end -end -end -end -self:T2(self.lid..string.format("Recruited %d assets for Mission type=%s",#assets,MissionType)) -return assets,Npayloads -end -function COHORT:GetRepairTime(Asset) -if Asset.Treturned then -local t=self.maintenancetime -t=t+Asset.damage*self.repairtime -local dt=timer.getAbsTime()-Asset.Treturned -local T=t-dt -return T -else -return 0 -end -end -function COHORT:GetMissionRange(WeaponTypes) -if WeaponTypes and type(WeaponTypes)~="table"then -WeaponTypes={WeaponTypes} -end -local function checkWeaponType(Weapon) -local weapon=Weapon -if WeaponTypes and#WeaponTypes>0 then -for _,weapontype in pairs(WeaponTypes)do -if weapontype==weapon.BitType then -return true -end -end -return false -end -return true -end -local WeaponRange=0 -for _,_weapon in pairs(self.weaponData or{})do -local weapon=_weapon -if weapon.RangeMax>WeaponRange and checkWeaponType(weapon)then -WeaponRange=weapon.RangeMax -end -end -return self.engageRange+WeaponRange -end -function COHORT:IsRepaired(Asset) -if Asset.Treturned then -local Tnow=timer.getAbsTime() -local Trepaired=Asset.Treturned+self.maintenancetime -if Tnow>=Trepaired then -return true -else -return false -end -else -return true -end -end -function COHORT:CheckAttribute(Attributes) -if type(Attributes)~="table"then -Attributes={Attributes} -end -for _,attribute in pairs(Attributes)do -if attribute==self.attribute then -return true -end -end -return false -end -function COHORT:_CheckAmmo() -local units=self.templategroup:GetUnits() -local nammo=0 -local nguns=0 -local nshells=0 -local nrockets=0 -local nmissiles=0 -local nmissilesAA=0 -local nmissilesAG=0 -local nmissilesAS=0 -local nmissilesSA=0 -local nmissilesBM=0 -local nmissilesCR=0 -local ntorps=0 -local nbombs=0 -for _,_unit in pairs(units)do -local unit=_unit -local text=string.format("Unit %s:\n",unit:GetName()) -local ammotable=unit:GetAmmo() -if ammotable then -self:T3(ammotable) -for w=1,#ammotable do -local weapon=ammotable[w] -local Desc=weapon["desc"] -local Warhead=Desc["warhead"] -local Nammo=weapon["count"] -local Category=Desc["category"] -local MissileCategory=(Category==Weapon.Category.MISSILE)and Desc.missileCategory or nil -local TypeName=Desc["typeName"] -local weaponString=UTILS.Split(TypeName,"%.") -local WeaponName=weaponString[#weaponString] -local Rmin=Desc["rangeMin"]or 0 -local Rmax=Desc["rangeMaxAltMin"]or 0 -local Caliber=Warhead and Warhead["caliber"]or 0 -if Category==Weapon.Category.SHELL then -if Caliber<70 then -nguns=nguns+Nammo -else -nshells=nshells+Nammo -end -text=text..string.format("- %d shells [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,WeaponName,Caliber,Rmin,Rmax) -elseif Category==Weapon.Category.ROCKET then -nrockets=nrockets+Nammo -text=text..string.format("- %d rockets [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,WeaponName,Caliber,Rmin,Rmax) -elseif Category==Weapon.Category.BOMB then -nbombs=nbombs+Nammo -text=text..string.format("- %d bombs [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,WeaponName,Caliber,Rmin,Rmax) -elseif Category==Weapon.Category.MISSILE then -if MissileCategory==Weapon.MissileCategory.AAM then -nmissiles=nmissiles+Nammo -nmissilesAA=nmissilesAA+Nammo -if Rmax>0 then -self:AddWeaponRange(UTILS.MetersToNM(Rmin),UTILS.MetersToNM(Rmax),ENUMS.WeaponFlag.AnyAA) -end -elseif MissileCategory==Weapon.MissileCategory.SAM then -nmissiles=nmissiles+Nammo -nmissilesSA=nmissilesSA+Nammo -if Rmax>0 then -end -elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then -nmissiles=nmissiles+Nammo -nmissilesAS=nmissilesAS+Nammo -if Rmax>0 then -self:AddWeaponRange(UTILS.MetersToNM(Rmin),UTILS.MetersToNM(Rmax),ENUMS.WeaponFlag.AntiShipMissile) -end -elseif MissileCategory==Weapon.MissileCategory.BM then -nmissiles=nmissiles+Nammo -nmissilesBM=nmissilesBM+Nammo -if Rmax>0 then -end -elseif MissileCategory==Weapon.MissileCategory.CRUISE then -nmissiles=nmissiles+Nammo -nmissilesCR=nmissilesCR+Nammo -if Rmax>0 then -self:AddWeaponRange(UTILS.MetersToNM(Rmin),UTILS.MetersToNM(Rmax),ENUMS.WeaponFlag.CruiseMissile) -end -elseif MissileCategory==Weapon.MissileCategory.OTHER then -nmissiles=nmissiles+Nammo -nmissilesAG=nmissilesAG+Nammo -end -text=text..string.format("- %d %s missiles [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,self:_MissileCategoryName(MissileCategory),WeaponName,Caliber,Rmin,Rmax) -elseif Category==Weapon.Category.TORPEDO then -ntorps=ntorps+Nammo -text=text..string.format("- %d torpedos [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,WeaponName,Caliber,Rmin,Rmax) -else -text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n",Nammo,TypeName,Category,tostring(MissileCategory)) -end -end -end -if self.verbose>=5 then -self:I(self.lid..text) -else -self:T2(self.lid..text) -end -end -nammo=nguns+nshells+nrockets+nmissiles+nbombs+ntorps -local ammo={} -ammo.Total=nammo -ammo.Guns=nguns -ammo.Shells=nshells -ammo.Rockets=nrockets -ammo.Bombs=nbombs -ammo.Torpedos=ntorps -ammo.Missiles=nmissiles -ammo.MissilesAA=nmissilesAA -ammo.MissilesAG=nmissilesAG -ammo.MissilesAS=nmissilesAS -ammo.MissilesCR=nmissilesCR -ammo.MissilesBM=nmissilesBM -ammo.MissilesSA=nmissilesSA -return ammo -end -function COHORT:_MissileCategoryName(categorynumber) -local cat="unknown" -if categorynumber==Weapon.MissileCategory.AAM then -cat="air-to-air" -elseif categorynumber==Weapon.MissileCategory.SAM then -cat="surface-to-air" -elseif categorynumber==Weapon.MissileCategory.BM then -cat="ballistic" -elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then -cat="anti-ship" -elseif categorynumber==Weapon.MissileCategory.CRUISE then -cat="cruise" -elseif categorynumber==Weapon.MissileCategory.OTHER then -cat="other" -end -return cat -end -function COHORT:_AddOperation(Operation) -self.operations[Operation.name]=Operation -end -COMMANDER={ -ClassName="COMMANDER", -verbose=0, -coalition=nil, -legions={}, -missionqueue={}, -transportqueue={}, -targetqueue={}, -opsqueue={}, -rearmingZones={}, -refuellingZones={}, -capZones={}, -gcicapZones={}, -awacsZones={}, -tankerZones={}, -limitMission={}, -} -COMMANDER.version="0.1.3" -function COMMANDER:New(Coalition,Alias) -local self=BASE:Inherit(self,FSM:New()) -if Coalition==nil then -env.error("ERROR: Coalition parameter is nil in COMMANDER:New() call!") -return nil -end -self.coalition=Coalition -self.alias=Alias -if self.alias==nil then -if Coalition==coalition.side.BLUE then -self.alias="George S. Patton" -elseif Coalition==coalition.side.RED then -self.alias="Georgy Zhukov" -elseif Coalition==coalition.side.NEUTRAL then -self.alias="Mahatma Gandhi" -end -end -self.lid=string.format("COMMANDER %s [%s] | ",self.alias,UTILS.GetCoalitionName(self.coalition)) -self:SetStartState("NotReadyYet") -self:AddTransition("NotReadyYet","Start","OnDuty") -self:AddTransition("*","Status","*") -self:AddTransition("*","Stop","Stopped") -self:AddTransition("*","MissionAssign","*") -self:AddTransition("*","MissionCancel","*") -self:AddTransition("*","TransportAssign","*") -self:AddTransition("*","TransportCancel","*") -self:AddTransition("*","OpsOnMission","*") -self:AddTransition("*","LegionLost","*") -return self -end -function COMMANDER:SetVerbosity(VerbosityLevel) -self.verbose=VerbosityLevel or 0 -return self -end -function COMMANDER:SetLimitMission(Limit,MissionType) -MissionType=MissionType or"Total" -if MissionType then -self.limitMission[MissionType]=Limit or 10 -else -self:E(self.lid.."ERROR: No mission type given for setting limit!") -end -return self -end -function COMMANDER:GetCoalition() -return self.coalition -end -function COMMANDER:AddAirwing(Airwing) -self:AddLegion(Airwing) -return self -end -function COMMANDER:AddBrigade(Brigade) -self:AddLegion(Brigade) -return self -end -function COMMANDER:AddFleet(Fleet) -self:AddLegion(Fleet) -return self -end -function COMMANDER:AddLegion(Legion) -Legion.commander=self -table.insert(self.legions,Legion) -return self -end -function COMMANDER:RemoveLegion(Legion) -for i,_legion in pairs(self.legions)do -local legion=_legion -if legion.alias==Legion.alias then -table.remove(self.legions,i) -Legion.commander=nil -end -end -return self -end -function COMMANDER:AddMission(Mission) -if not self:IsMission(Mission)then -Mission.commander=self -Mission.statusCommander=AUFTRAG.Status.PLANNED -table.insert(self.missionqueue,Mission) -end -return self -end -function COMMANDER:AddOpsTransport(Transport) -Transport.commander=self -Transport.statusCommander=OPSTRANSPORT.Status.PLANNED -table.insert(self.transportqueue,Transport) -return self -end -function COMMANDER:RemoveMission(Mission) -for i,_mission in pairs(self.missionqueue)do -local mission=_mission -if mission.auftragsnummer==Mission.auftragsnummer then -self:T(self.lid..string.format("Removing mission %s (%s) status=%s from queue",Mission.name,Mission.type,Mission.status)) -mission.commander=nil -table.remove(self.missionqueue,i) -break -end -end -return self -end -function COMMANDER:RemoveTransport(Transport) -for i,_transport in pairs(self.transportqueue)do -local transport=_transport -if transport.uid==Transport.uid then -self:T(self.lid..string.format("Removing transport UID=%d status=%s from queue",transport.uid,transport:GetState())) -transport.commander=nil -table.remove(self.transportqueue,i) -break -end -end -return self -end -function COMMANDER:AddTarget(Target) -if not self:IsTarget(Target)then -table.insert(self.targetqueue,Target) -end -return self -end -function COMMANDER:AddOperation(Operation) -table.insert(self.opsqueue,Operation) -return self -end -function COMMANDER:IsTarget(Target) -for _,_target in pairs(self.targetqueue)do -local target=_target -if target.uid==Target.uid or target:GetName()==Target:GetName()then -return true -end -end -return false -end -function COMMANDER:RemoveTarget(Target) -for i,_target in pairs(self.targetqueue)do -local target=_target -if target.uid==Target.uid then -self:T(self.lid..string.format("Removing target %s from queue",Target.name)) -table.remove(self.targetqueue,i) -break -end -end -return self -end -function COMMANDER:AddRearmingZone(RearmingZone) -local rearmingzone={} -rearmingzone.zone=RearmingZone -rearmingzone.mission=nil -table.insert(self.rearmingZones,rearmingzone) -return rearmingzone -end -function COMMANDER:AddRefuellingZone(RefuellingZone) -local rearmingzone={} -rearmingzone.zone=RefuellingZone -rearmingzone.mission=nil -table.insert(self.refuellingZones,rearmingzone) -return rearmingzone -end -function COMMANDER:AddCapZone(Zone,Altitude,Speed,Heading,Leg) -local patrolzone={} -patrolzone.zone=Zone -patrolzone.altitude=Altitude or 12000 -patrolzone.heading=Heading or 270 -patrolzone.speed=UTILS.KnotsToAltKIAS(Speed or 350,patrolzone.altitude) -patrolzone.leg=Leg or 30 -patrolzone.mission=nil -table.insert(self.capZones,patrolzone) -return patrolzone -end -function COMMANDER:AddGciCapZone(Zone,Altitude,Speed,Heading,Leg) -local patrolzone={} -patrolzone.zone=Zone -patrolzone.altitude=Altitude or 12000 -patrolzone.heading=Heading or 270 -patrolzone.speed=UTILS.KnotsToAltKIAS(Speed or 350,patrolzone.altitude) -patrolzone.leg=Leg or 30 -patrolzone.mission=nil -table.insert(self.gcicapZones,patrolzone) -return patrolzone -end -function COMMANDER:RemoveGciCapZone(Zone) -local patrolzone={} -patrolzone.zone=Zone -for i,_patrolzone in pairs(self.gcicapZones)do -if _patrolzone.zone==patrolzone.zone then -if _patrolzone.mission and _patrolzone.mission:IsNotOver()then -_patrolzone.mission:Cancel() -end -table.remove(self.gcicapZones,i) -break -end -end -return patrolzone -end -function COMMANDER:AddAwacsZone(Zone,Altitude,Speed,Heading,Leg) -local awacszone={} -awacszone.zone=Zone -awacszone.altitude=Altitude or 12000 -awacszone.heading=Heading or 270 -awacszone.speed=UTILS.KnotsToAltKIAS(Speed or 350,awacszone.altitude) -awacszone.leg=Leg or 30 -awacszone.mission=nil -table.insert(self.awacsZones,awacszone) -return awacszone -end -function COMMANDER:RemoveAwacsZone(Zone) -local awacszone={} -awacszone.zone=Zone -for i,_awacszone in pairs(self.awacsZones)do -if _awacszone.zone==awacszone.zone then -if _awacszone.mission and _awacszone.mission:IsNotOver()then -_awacszone.mission:Cancel() -end -table.remove(self.awacsZones,i) -break -end -end -return awacszone -end -function COMMANDER:AddTankerZone(Zone,Altitude,Speed,Heading,Leg,RefuelSystem) -local tankerzone={} -tankerzone.zone=Zone -tankerzone.altitude=Altitude or 12000 -tankerzone.heading=Heading or 270 -tankerzone.speed=UTILS.KnotsToAltKIAS(Speed or 350,tankerzone.altitude) -tankerzone.leg=Leg or 30 -tankerzone.refuelsystem=RefuelSystem -tankerzone.mission=nil -tankerzone.marker=MARKER:New(tankerzone.zone:GetCoordinate(),"Tanker Zone"):ToCoalition(self:GetCoalition()) -table.insert(self.tankerZones,tankerzone) -return tankerzone -end -function COMMANDER:RemoveTankerZone(Zone) -local tankerzone={} -tankerzone.zone=Zone -for i,_tankerzone in pairs(self.tankerZones)do -if _tankerzone.zone==tankerzone.zone then -if _tankerzone.mission and _tankerzone.mission:IsNotOver()then -_tankerzone.mission:Cancel() -end -table.remove(self.tankerZones,i) -break -end -end -return tankerzone -end -function COMMANDER:IsMission(Mission) -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if mission.auftragsnummer==Mission.auftragsnummer then -return true -end -end -return false -end -function COMMANDER:RelocateCohort(Cohort,Legion,Delay,NcarriersMin,NcarriersMax,TransportLegions) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,COMMANDER.RelocateCohort,self,Cohort,Legion,0,NcarriersMin,NcarriersMax,TransportLegions) -else -if Legion:IsCohort(Cohort.name)then -self:E(self.lid..string.format("ERROR: Cohort %s is already part of new legion %s ==> CANNOT Relocate!",Cohort.name,Legion.alias)) -return self -else -table.insert(Legion.cohorts,Cohort) -end -local LegionOld=Cohort.legion -if not LegionOld:IsCohort(Cohort.name)then -self:E(self.lid..string.format("ERROR: Cohort %s is NOT part of this legion %s ==> CANNOT Relocate!",Cohort.name,self.alias)) -return self -end -if LegionOld.alias==Legion.alias then -self:E(self.lid..string.format("ERROR: old legion %s is same as new legion %s ==> CANNOT Relocate!",LegionOld.alias,Legion.alias)) -return self -end -Cohort:Relocate() -local mission=AUFTRAG:_NewRELOCATECOHORT(Legion,Cohort) -mission:AssignCohort(Cohort) -mission:SetRequiredAssets(#Cohort.assets) -if NcarriersMin and NcarriersMin>0 then -mission:SetRequiredTransport(Legion.spawnzone,NcarriersMin,NcarriersMax) -end -if TransportLegions then -for _,legion in pairs(TransportLegions)do -mission:AssignTransportLegion(legion) -end -else -for _,legion in pairs(self.legions)do -mission:AssignTransportLegion(legion) -end -end -mission:SetMissionRange(10000) -self:AddMission(mission) -end -return self -end -function COMMANDER:onafterStart(From,Event,To) -local text=string.format("Starting Commander") -self:I(self.lid..text) -for _,_legion in pairs(self.legions)do -local legion=_legion -if legion:GetState()=="NotReadyYet"then -legion:Start() -end -end -self:__Status(-1) -end -function COMMANDER:onafterStatus(From,Event,To) -local fsmstate=self:GetState() -if self.verbose>=1 then -local text=string.format("Status %s: Legions=%d, Missions=%d, Targets=%d, Transports=%d",fsmstate,#self.legions,#self.missionqueue,#self.targetqueue,#self.transportqueue) -self:T(self.lid..text) -end -self:CheckOpsQueue() -self:CheckTargetQueue() -self:CheckMissionQueue() -self:CheckTransportQueue() -for _,_rearmingzone in pairs(self.rearmingZones)do -local rearmingzone=_rearmingzone -if(not rearmingzone.mission)or rearmingzone.mission:IsOver()then -rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone) -self:AddMission(rearmingzone.mission) -end -end -for _,_supplyzone in pairs(self.refuellingZones)do -local supplyzone=_supplyzone -if(not supplyzone.mission)or supplyzone.mission:IsOver()then -supplyzone.mission=AUFTRAG:NewFUELSUPPLY(supplyzone.zone) -self:AddMission(supplyzone.mission) -end -end -for _,_patrolzone in pairs(self.capZones)do -local patrolzone=_patrolzone -if(not patrolzone.mission)or patrolzone.mission:IsOver()then -local Coordinate=patrolzone.zone:GetCoordinate() -patrolzone.mission=AUFTRAG:NewCAP(patrolzone.zone,patrolzone.altitude,patrolzone.speed,Coordinate,patrolzone.heading,patrolzone.leg) -self:AddMission(patrolzone.mission) -end -end -for _,_patrolzone in pairs(self.gcicapZones)do -local patrolzone=_patrolzone -if(not patrolzone.mission)or patrolzone.mission:IsOver()then -local Coordinate=patrolzone.zone:GetCoordinate() -patrolzone.mission=AUFTRAG:NewGCICAP(Coordinate,patrolzone.altitude,patrolzone.speed,patrolzone.heading,patrolzone.leg) -self:AddMission(patrolzone.mission) -end -end -for _,_awacszone in pairs(self.awacsZones)do -local awacszone=_awacszone -if(not awacszone.mission)or awacszone.mission:IsOver()then -local Coordinate=awacszone.zone:GetCoordinate() -awacszone.mission=AUFTRAG:NewAWACS(Coordinate,awacszone.altitude,awacszone.speed,awacszone.heading,awacszone.leg) -self:AddMission(awacszone.mission) -end -end -for _,_tankerzone in pairs(self.tankerZones)do -local tankerzone=_tankerzone -if(not tankerzone.mission)or tankerzone.mission:IsOver()then -local Coordinate=tankerzone.zone:GetCoordinate() -tankerzone.mission=AUFTRAG:NewTANKER(Coordinate,tankerzone.altitude,tankerzone.speed,tankerzone.heading,tankerzone.leg,tankerzone.refuelsystem) -self:AddMission(tankerzone.mission) -end -end -if self.verbose>=2 and#self.legions>0 then -local text="Legions:" -for _,_legion in pairs(self.legions)do -local legion=_legion -local Nassets=legion:CountAssets() -local Nastock=legion:CountAssets(true) -text=text..string.format("\n* %s [%s]: Assets=%s stock=%s",legion.alias,legion:GetState(),Nassets,Nastock) -for _,aname in pairs(AUFTRAG.Type)do -local na=legion:CountAssets(true,{aname}) -local np=legion:CountPayloadsInStock({aname}) -local nm=legion:CountAssetsOnMission({aname}) -if na>0 or np>0 then -text=text..string.format("\n - %s: assets=%d, payloads=%d, on mission=%d",aname,na,np,nm) -end -end -end -self:T(self.lid..text) -if self.verbose>=3 then -local Ntotal=0 -local Nspawned=0 -local Nrequested=0 -local Nreserved=0 -local Nstock=0 -local text="\n===========================================\n" -text=text.."Assets:" -for _,_legion in pairs(self.legions)do -local legion=_legion -for _,_cohort in pairs(legion.cohorts)do -local cohort=_cohort -for _,_asset in pairs(cohort.assets)do -local asset=_asset -local state="In Stock" -if asset.flightgroup then -state=asset.flightgroup:GetState() -local mission=legion:GetAssetCurrentMission(asset) -if mission then -state=state..string.format(", Mission \"%s\" [%s]",mission:GetName(),mission:GetType()) -end -else -if asset.spawned then -env.info("FF ERROR: asset has opsgroup but is NOT spawned!") -end -if asset.requested and asset.isReserved then -env.info("FF ERROR: asset is requested and reserved. Should not be both!") -state="Reserved+Requested!" -elseif asset.isReserved then -state="Reserved" -elseif asset.requested then -state="Requested" -end -end -text=text..string.format("\n[UID=%03d] %s Legion=%s [%s]: State=%s [RID=%s]", -asset.uid,asset.spawngroupname,legion.alias,cohort.name,state,tostring(asset.rid)) -if asset.spawned then -Nspawned=Nspawned+1 -end -if asset.requested then -Nrequested=Nrequested+1 -end -if asset.isReserved then -Nreserved=Nreserved+1 -end -if not(asset.spawned or asset.requested or asset.isReserved)then -Nstock=Nstock+1 -end -Ntotal=Ntotal+1 -end -end -end -text=text.."\n-------------------------------------------" -text=text..string.format("\nNstock = %d",Nstock) -text=text..string.format("\nNreserved = %d",Nreserved) -text=text..string.format("\nNrequested = %d",Nrequested) -text=text..string.format("\nNspawned = %d",Nspawned) -text=text..string.format("\nNtotal = %d (=%d)",Ntotal,Nstock+Nspawned+Nrequested+Nreserved) -text=text.."\n===========================================" -self:I(self.lid..text) -end -end -if self.verbose>=2 and#self.missionqueue>0 then -local text="Mission queue:" -for i,_mission in pairs(self.missionqueue)do -local mission=_mission -local target=mission:GetTargetName()or"unknown" -text=text..string.format("\n[%d] %s (%s): status=%s, target=%s",i,mission.name,mission.type,mission.status,target) -end -self:I(self.lid..text) -end -if self.verbose>=2 and#self.targetqueue>0 then -local text="Target queue:" -for i,_target in pairs(self.targetqueue)do -local target=_target -text=text..string.format("\n[%d] %s: status=%s, life=%d",i,target:GetName(),target:GetState(),target:GetLife()) -end -self:I(self.lid..text) -end -if self.verbose>=2 and#self.transportqueue>0 then -local text="Transport queue:" -for i,_transport in pairs(self.transportqueue)do -local transport=_transport -text=text..string.format("\n[%d] UID=%d: status=%s",i,transport.uid,transport:GetState()) -end -self:I(self.lid..text) -end -self:__Status(-30) -end -function COMMANDER:onafterMissionAssign(From,Event,To,Mission,Legions) -self:AddMission(Mission) -Mission.statusCommander=AUFTRAG.Status.QUEUED -for _,_Legion in pairs(Legions)do -local Legion=_Legion -self:T(self.lid..string.format("Assigning mission \"%s\" [%s] to legion \"%s\"",Mission.name,Mission.type,Legion.alias)) -Legion:AddMission(Mission) -Legion:MissionRequest(Mission) -end -end -function COMMANDER:onafterMissionCancel(From,Event,To,Mission) -self:T(self.lid..string.format("Cancelling mission \"%s\" [%s] in status %s",Mission.name,Mission.type,Mission.status)) -Mission.statusCommander=AUFTRAG.Status.CANCELLED -if Mission:IsPlanned()then -self:RemoveMission(Mission) -else -if#Mission.legions>0 then -for _,_legion in pairs(Mission.legions)do -local legion=_legion -legion:MissionCancel(Mission) -end -end -end -end -function COMMANDER:onafterTransportAssign(From,Event,To,Transport,Legions) -Transport.statusCommander=OPSTRANSPORT.Status.QUEUED -for _,_Legion in pairs(Legions)do -local Legion=_Legion -self:T(self.lid..string.format("Assigning transport UID=%d to legion \"%s\"",Transport.uid,Legion.alias)) -Legion:AddOpsTransport(Transport) -Legion:TransportRequest(Transport) -end -end -function COMMANDER:onafterTransportCancel(From,Event,To,Transport) -self:T(self.lid..string.format("Cancelling Transport UID=%d in status %s",Transport.uid,Transport:GetState())) -Transport.statusCommander=OPSTRANSPORT.Status.CANCELLED -if Transport:IsPlanned()then -self:RemoveTransport(Transport) -else -if#Transport.legions>0 then -for _,_legion in pairs(Transport.legions)do -local legion=_legion -legion:TransportCancel(Transport) -end -end -end -end -function COMMANDER:onafterOpsOnMission(From,Event,To,OpsGroup,Mission) -self:T2(self.lid..string.format("Group \"%s\" on mission \"%s\" [%s]",OpsGroup:GetName(),Mission:GetName(),Mission:GetType())) -end -function COMMANDER:CheckOpsQueue() -local Nops=#self.opsqueue -if Nops==0 then -return nil -end -for _,_ops in pairs(self.opsqueue)do -local operation=_ops -if operation:IsRunning()then -for _,_mission in pairs(operation.missions or{})do -local mission=_mission -if mission.phase==nil or(mission.phase and mission.phase==operation.phase)and mission:IsPlanned()then -self:AddMission(mission) -end -end -for _,_target in pairs(operation.targets or{})do -local target=_target -if(target.phase==nil or(target.phase and target.phase==operation.phase))and(not self:IsTarget(target))then -self:AddTarget(target) -end -end -end -end -end -function COMMANDER:CheckTargetQueue() -local Ntargets=#self.targetqueue -if Ntargets==0 then -return nil -end -for i=#self.targetqueue,1,-1 do -local target=self.targetqueue[i] -if(not target:IsAlive())or target:EvalConditionsAny(target.conditionStop)then -for _,_resource in pairs(target.resources)do -local resource=_resource -if resource.mission and resource.mission:IsNotOver()then -self:MissionCancel(resource.mission) -end -end -table.remove(self.targetqueue,i) -end -end -local NoLimit=self:_CheckMissionLimit("Total") -if NoLimit==false then -return nil -end -local function _sort(a,b) -local taskA=a -local taskB=b -return(taskA.priotaskB.threatlevel0) -end -table.sort(self.targetqueue,_sort) -local vip=math.huge -for _,_target in pairs(self.targetqueue)do -local target=_target -if target:IsAlive()and target.importance and target.importance Creating mission type %s: Nmin=%d, Nmax=%d",target:GetName(),missionType,resource.Nmin,resource.Nmax)) -local mission=AUFTRAG:NewFromTarget(target,missionType) -if mission then -mission:SetRequiredAssets(resource.Nmin,resource.Nmax) -mission:SetRequiredAttribute(resource.Attributes) -mission:SetRequiredProperty(resource.Properties) -mission.operation=target.operation -resource.mission=mission -self:AddMission(resource.mission) -end -end -end -end -end -end -function COMMANDER:CheckMissionQueue() -local Nmissions=#self.missionqueue -if Nmissions==0 then -return nil -end -local NoLimit=self:_CheckMissionLimit("Total") -if NoLimit==false then -return nil -end -local function _sort(a,b) -local taskA=a -local taskB=b -return(taskA.prio0)or(Cohorts and#Cohorts>0)then -for _,_legion in pairs(Legions or{})do -local legion=_legion -local Runway=legion:IsAirwing()and legion:IsRunwayOperational()or true -if legion:IsRunning()and Runway then -for _,_cohort in pairs(legion.cohorts)do -local cohort=_cohort -if CheckOperation(cohort.legion)or CheckOperation(cohort)then -table.insert(cohorts,cohort) -end -end -end -end -for _,_cohort in pairs(Cohorts or{})do -local cohort=_cohort -if CheckOperation(cohort)then -table.insert(cohorts,cohort) -end -end -else -for _,_legion in pairs(self.legions)do -local legion=_legion -local Runway=legion:IsAirwing()and legion:IsRunwayOperational()or true -if legion:IsRunning()and Runway then -for _,_cohort in pairs(legion.cohorts)do -local cohort=_cohort -if CheckOperation(cohort.legion)or CheckOperation(cohort)then -table.insert(cohorts,cohort) -end -end -end -end -end -return cohorts -end -function COMMANDER:RecruitAssetsForMission(Mission) -self:T2(self.lid..string.format("Recruiting assets for mission \"%s\" [%s]",Mission:GetName(),Mission:GetType())) -local NreqMin,NreqMax=Mission:GetRequiredAssets() -local TargetVec2=Mission:GetTargetVec2() -local Payloads=Mission.payloads -local MaxWeight=nil -if Mission.NcarriersMin then -local legions=self.legions -local cohorts=nil -if Mission.transportLegions or Mission.transportCohorts then -legions=Mission.transportLegions -cohorts=Mission.transportCohorts -end -local Cohorts=LEGION._GetCohorts(legions,cohorts) -local transportcohorts={} -for _,_cohort in pairs(Cohorts)do -local cohort=_cohort -local can=LEGION._CohortCan(cohort,AUFTRAG.Type.OPSTRANSPORT,Mission.carrierCategories,Mission.carrierAttributes,Mission.carrierProperties,nil,TargetVec2) -if can and(MaxWeight==nil or cohort.cargobayLimit>MaxWeight)then -MaxWeight=cohort.cargobayLimit -end -end -self:T(self.lid..string.format("Largest cargo bay available=%.1f",MaxWeight)) -end -local legions=self.legions -local cohorts=nil -if Mission.specialLegions or Mission.specialCohorts then -legions=Mission.specialLegions -cohorts=Mission.specialCohorts -end -local Cohorts=LEGION._GetCohorts(legions,cohorts,Mission.operation,self.opsqueue) -self:T(self.lid..string.format("Found %d cohort candidates for mission",#Cohorts)) -local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,Mission.type,Mission.alert5MissionType,NreqMin,NreqMax,TargetVec2,Payloads, -Mission.engageRange,Mission.refuelSystem,nil,nil,MaxWeight,nil,Mission.attributes,Mission.properties,{Mission.engageWeaponType}) -return recruited,assets,legions -end -function COMMANDER:RecruitAssetsForEscort(Mission,Assets) -if Mission.NescortMin and Mission.NescortMax and(Mission.NescortMin>0 or Mission.NescortMax>0)then -local Cohorts=self:_GetCohorts(Mission.escortLegions,Mission.escortCohorts,Mission.operation) -local assigned=LEGION.AssignAssetsForEscort(self,Cohorts,Assets,Mission.NescortMin,Mission.NescortMax,Mission.escortMissionType,Mission.escortTargetTypes,Mission.escortEngageRange) -return assigned -end -return true -end -function COMMANDER:RecruitAssetsForTarget(Target,MissionType,NassetsMin,NassetsMax) -local Cohorts=self:_GetCohorts() -local TargetVec2=Target:GetVec2() -local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,MissionType,nil,NassetsMin,NassetsMax,TargetVec2) -return recruited,assets,legions -end -function COMMANDER:CheckTransportQueue() -local Ntransports=#self.transportqueue -if Ntransports==0 then -return nil -end -local function _sort(a,b) -local taskA=a -local taskB=b -return(taskA.prio0 then -for _,_opsgroup in pairs(cargoOpsGroups)do -local opsgroup=_opsgroup -local weight=opsgroup:GetWeightTotal() -if weight>weightGroup then -weightGroup=weight -end -TotalWeight=TotalWeight+weight -end -end -if weightGroup>0 then -local recruited,assets,legions=self:RecruitAssetsForTransport(transport,weightGroup,TotalWeight) -if recruited then -for _,_asset in pairs(assets)do -local asset=_asset -transport:AddAsset(asset) -end -self:TransportAssign(transport,legions) -return -else -LEGION.UnRecruitAssets(assets) -end -end -else -end -end -end -function COMMANDER:RecruitAssetsForTransport(Transport,CargoWeight,TotalWeight) -if CargoWeight==0 then -return false,{},{} -end -local Cohorts=self:_GetCohorts() -local TargetVec2=Transport:GetDeployZone():GetVec2() -local NreqMin,NreqMax=Transport:GetRequiredCarriers() -local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,AUFTRAG.Type.OPSTRANSPORT,nil,NreqMin,NreqMax,TargetVec2,nil,nil,nil,CargoWeight,TotalWeight) -return recruited,assets,legions -end -function COMMANDER:_CheckMissionLimit(MissionType) -local limit=self.limitMission[MissionType] -if limit then -if MissionType=="Total"then -MissionType=AUFTRAG.Type -end -local N=self:CountMissions(MissionType,true) -if N>=limit then -return false -end -end -return true -end -function COMMANDER:CountAssets(InStock,MissionTypes,Attributes) -local N=0 -for _,_legion in pairs(self.legions)do -local legion=_legion -N=N+legion:CountAssets(InStock,MissionTypes,Attributes) -end -return N -end -function COMMANDER:CountMissions(MissionTypes,OnlyRunning) -local N=0 -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if(not OnlyRunning)or(mission.statusCommander~=AUFTRAG.Status.PLANNED)then -if AUFTRAG.CheckMissionType(mission.type,MissionTypes)then -N=N+1 -end -end -end -return N -end -function COMMANDER:GetAssets(InStock,Legions,MissionTypes,Attributes) -local assets={} -for _,_legion in pairs(Legions or self.legions)do -local legion=_legion -for _,_cohort in pairs(legion.cohorts)do -local cohort=_cohort -for _,_asset in pairs(cohort.assets)do -local asset=_asset -if not(asset.spawned or asset.isReserved or asset.requested)then -table.insert(assets,asset) -end -end -end -end -return assets -end -function COMMANDER:GetLegionsForMission(Mission) -local legions={} -for _,_legion in pairs(self.legions)do -local legion=_legion -local Nassets=0 -if legion:IsAirwing()then -Nassets=legion:CountAssetsWithPayloadsInStock(Mission.payloads,{Mission.type},Attributes) -else -Nassets=legion:CountAssets(true,{Mission.type},Attributes) -end -if Nassets>0 and false then -local coord=Mission:GetTargetCoordinate() -if coord then -local distance=UTILS.MetersToNM(coord:Get2DDistance(legion:GetCoordinate())) -local dist=UTILS.Round(distance/10,0) -self:T(self.lid..string.format("Got legion %s with Nassets=%d and dist=%.1f NM, rounded=%.1f",legion.alias,Nassets,distance,dist)) -table.insert(legions,{airwing=legion,distance=distance,dist=dist,targetcoord=coord,nassets=Nassets}) -end -end -if Nassets>0 then -table.insert(legions,legion) -end -end -return legions -end -function COMMANDER:GetAssetsOnMission(MissionTypes) -local assets={} -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if AUFTRAG.CheckMissionType(mission.type,MissionTypes)then -for _,_asset in pairs(mission.assets or{})do -local asset=_asset -table.insert(assets,asset) -end -end -end -return assets -end -FLEET={ -ClassName="FLEET", -verbose=0, -pathfinding=false, -} -FLEET.version="0.0.1" -function FLEET:New(WarehouseName,FleetName) -local self=BASE:Inherit(self,LEGION:New(WarehouseName,FleetName)) -if not self then -BASE:E(string.format("ERROR: Could not find warehouse %s!",WarehouseName)) -return nil -end -self.lid=string.format("FLEET %s | ",self.alias) -self:SetRetreatZones() -if self:IsShip()then -local wh=self.warehouse -local group=wh:GetGroup() -self.warehouseOpsGroup=NAVYGROUP:New(group) -self.warehouseOpsElement=self.warehouseOpsGroup:GetElementByName(wh:GetName()) -end -self:AddTransition("*","NavyOnMission","*") -return self -end -function FLEET:AddFlotilla(Flotilla) -table.insert(self.cohorts,Flotilla) -self:AddAssetToFlotilla(Flotilla,Flotilla.Ngroups) -Flotilla:SetFleet(self) -if Flotilla:IsStopped()then -Flotilla:Start() -end -return self -end -function FLEET:AddAssetToFlotilla(Flotilla,Nassets) -if Flotilla then -local Group=GROUP:FindByName(Flotilla.templatename) -if Group then -local text=string.format("Adding asset %s to flotilla %s",Group:GetName(),Flotilla.name) -self:T(self.lid..text) -self:AddAsset(Group,Nassets,nil,nil,nil,nil,Flotilla.skill,Flotilla.livery,Flotilla.name) -else -self:E(self.lid.."ERROR: Group does not exist!") -end -else -self:E(self.lid.."ERROR: Flotilla does not exit!") -end -return self -end -function FLEET:SetPathfinding(Switch) -self.pathfinding=Switch -return self -end -function FLEET:SetRetreatZones(RetreatZoneSet) -self.retreatZones=RetreatZoneSet or SET_ZONE:New() -return self -end -function FLEET:AddRetreatZone(RetreatZone) -self.retreatZones:AddZone(RetreatZone) -return self -end -function FLEET:GetRetreatZones() -return self.retreatZones -end -function FLEET:GetFlotilla(FlotillaName) -local flotilla=self:_GetCohort(FlotillaName) -return flotilla -end -function FLEET:GetFlotillaOfAsset(Asset) -local flotilla=self:GetFlotilla(Asset.squadname) -return flotilla -end -function FLEET:RemoveAssetFromFlotilla(Asset) -local flotilla=self:GetFlotillaOfAsset(Asset) -if flotilla then -flotilla:DelAsset(Asset) -end -end -function FLEET:onafterStart(From,Event,To) -self:GetParent(self,FLEET).onafterStart(self,From,Event,To) -self:I(self.lid..string.format("Starting FLEET v%s",FLEET.version)) -end -function FLEET:onafterStatus(From,Event,To) -self:GetParent(self).onafterStatus(self,From,Event,To) -local fsmstate=self:GetState() -self:CheckTransportQueue() -self:CheckMissionQueue() -if self.verbose>=1 then -local Nmissions=self:CountMissionsInQueue() -local Npq,Np,Nq=self:CountAssetsOnMission() -local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]",self:CountAssets(),Npq,Np,Nq) -local text=string.format("%s: Missions=%d, Flotillas=%d, Assets=%s",fsmstate,Nmissions,#self.cohorts,assets) -self:I(self.lid..text) -end -if self.verbose>=2 then -local text=string.format("Missions Total=%d:",#self.missionqueue) -for i,_mission in pairs(self.missionqueue)do -local mission=_mission -local prio=string.format("%d/%s",mission.prio,tostring(mission.importance));if mission.urgent then prio=prio.." (!)"end -local assets=string.format("%d/%d",mission:CountOpsGroups(),mission.Nassets or 0) -local target=string.format("%d/%d Damage=%.1f",mission:CountMissionTargets(),mission:GetTargetInitialNumber(),mission:GetTargetDamage()) -text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s",i,mission.name,mission.type,mission.status,prio,assets,target) -end -self:I(self.lid..text) -end -if self.verbose>=2 then -local text=string.format("Transports Total=%d:",#self.transportqueue) -for i,_transport in pairs(self.transportqueue)do -local transport=_transport -local prio=string.format("%d/%s",transport.prio,tostring(transport.importance));if transport.urgent then prio=prio.." (!)"end -local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d",transport.Ncargo,transport.Ndelivered,transport.Ncarrier) -text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s",i,transport.uid,transport:GetState(),prio,carriers) -end -self:I(self.lid..text) -end -if self.verbose>=3 then -local text="Flotillas:" -for i,_flotilla in pairs(self.cohorts)do -local flotilla=_flotilla -local callsign=flotilla.callsignName and UTILS.GetCallsignName(flotilla.callsignName)or"N/A" -local modex=flotilla.modex and flotilla.modex or-1 -local skill=flotilla.skill and tostring(flotilla.skill)or"N/A" -text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s",flotilla.name,flotilla:GetState(),flotilla.aircrafttype,flotilla:CountAssets(true),#flotilla.assets,callsign,modex,skill) -end -self:I(self.lid..text) -end -end -function FLEET:onafterNavyOnMission(From,Event,To,NavyGroup,Mission) -self:T(self.lid..string.format("Group %s on %s mission %s",NavyGroup:GetName(),Mission:GetType(),Mission:GetName())) -end -FLIGHTCONTROL={ -ClassName="FLIGHTCONTROL", -verbose=0, -lid=nil, -theatre=nil, -airbasename=nil, -airbase=nil, -airbasetype=nil, -zoneAirbase=nil, -parking={}, -runways={}, -flights={}, -clients={}, -atis=nil, -Nlanding=nil, -dTlanding=nil, -Nparkingspots=nil, -holdingpatterns={}, -hpcounter=0, -nosubs=false, -} -FLIGHTCONTROL.FlightStatus={ -UNKNOWN="Unknown", -PARKING="Parking", -READYTX="Ready To Taxi", -TAXIOUT="Taxi To Runway", -READYTO="Ready For Takeoff", -TAKEOFF="Takeoff", -INBOUND="Inbound", -HOLDING="Holding", -LANDING="Landing", -TAXIINB="Taxi To Parking", -ARRIVED="Arrived", -} -FLIGHTCONTROL.version="0.7.5" -function FLIGHTCONTROL:New(AirbaseName,Frequency,Modulation,PathToSRS,Port,GoogleKey) -local self=BASE:Inherit(self,FSM:New()) -self.airbase=AIRBASE:FindByName(AirbaseName) -self.airbasename=AirbaseName -self.lid=string.format("FLIGHTCONTROL %s | ",AirbaseName) -if not self.airbase then -self:E(string.format("ERROR: Could not find airbase %s!",tostring(AirbaseName))) -return nil -end -if self.airbase:GetAirbaseCategory()~=Airbase.Category.AIRDROME then -self:E(string.format("ERROR: Airbase %s is not an AIRDROME! Script does not handle FARPS or ships.",tostring(AirbaseName))) -return nil -end -self.airbasetype=self.airbase:GetAirbaseCategory() -self.theatre=env.mission.theatre -self.zoneAirbase=ZONE_RADIUS:New("FC",self:GetCoordinate():GetVec2(),UTILS.NMToMeters(5)) -self:_AddHoldingPatternBackup() -self.alias=self.airbasename.." Tower" -self:SetLimitLanding(2,0) -self:SetLimitTaxi(2,false,0) -self:SetLandingInterval() -self:SetFrequency(Frequency,Modulation) -self:SetMarkHoldingPattern(true) -self:SetRunwayRepairtime() -self.nosubs=false -self:SetCallSignOptions(true,true) -self.msrsqueue=MSRSQUEUE:New(self.alias) -local path=PathToSRS or MSRS.path -local port=Port or MSRS.port or 5002 -self:SetSRSPort(port) -self.msrsTower=MSRS:New(path,Frequency,Modulation) -self.msrsTower:SetPort(port) -if GoogleKey then -self.msrsTower:SetProviderOptionsGoogle(GoogleKey,GoogleKey) -self.msrsTower:SetProvider(MSRS.Provider.GOOGLE) -end -self.msrsTower:SetCoordinate(self:GetCoordinate()) -self:SetSRSTower() -self.msrsPilot=MSRS:New(PathToSRS,Frequency,Modulation) -self.msrsPilot:SetPort(self.Port) -if GoogleKey then -self.msrsPilot:SetProviderOptionsGoogle(GoogleKey,GoogleKey) -self.msrsPilot:SetProvider(MSRS.Provider.GOOGLE) -end -self.msrsTower:SetCoordinate(self:GetCoordinate()) -self:SetSRSPilot() -self.dTmessage=10 -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","StatusUpdate","*") -self:AddTransition("*","PlayerKilledGuard","*") -self:AddTransition("*","PlayerSpeeding","*") -self:AddTransition("*","RunwayDestroyed","*") -self:AddTransition("*","RunwayRepaired","*") -self:AddTransition("*","Stop","Stopped") -_DATABASE:AddFlightControl(self) -return self -end -function FLIGHTCONTROL:SetVerbosity(VerbosityLevel) -self.verbose=VerbosityLevel or 0 -return self -end -function FLIGHTCONTROL:SwitchSubtitlesOn() -self.nosubs=false -return self -end -function FLIGHTCONTROL:SwitchSubtitlesOff() -self.nosubs=true -return self -end -function FLIGHTCONTROL:SetFrequency(Frequency,Modulation) -self.frequency=Frequency or 305 -self.modulation=Modulation or radio.modulation.AM -if self.msrsPilot then -self.msrsPilot:SetFrequencies(Frequency) -self.msrsPilot:SetModulations(Modulation) -end -if self.msrsTower then -self.msrsTower:SetFrequencies(Frequency) -self.msrsTower:SetModulations(Modulation) -end -return self -end -function FLIGHTCONTROL:SetSRSPort(Port) -self.Port=Port or 5002 -return self -end -function FLIGHTCONTROL:_SetSRSOptions(msrs,Gender,Culture,Voice,Volume,Label,PathToGoogleCredentials,Port) -Gender=Gender or"female" -Culture=Culture or"en-GB" -Volume=Volume or 1.0 -if msrs then -msrs:SetGender(Gender) -msrs:SetCulture(Culture) -msrs:SetVoice(Voice) -msrs:SetVolume(Volume) -msrs:SetLabel(Label) -msrs:SetCoalition(self:GetCoalition()) -msrs:SetPort(Port or self.Port or 5002) -end -return self -end -function FLIGHTCONTROL:SetSRSTower(Gender,Culture,Voice,Volume,Label) -if self.msrsTower then -self:_SetSRSOptions(self.msrsTower,Gender or"female",Culture or"en-GB",Voice,Volume,Label or self.alias) -end -return self -end -function FLIGHTCONTROL:SetSRSPilot(Gender,Culture,Voice,Volume,Label) -if self.msrsPilot then -self:_SetSRSOptions(self.msrsPilot,Gender or"male",Culture or"en-US",Voice,Volume,Label or"Pilot") -end -return self -end -function FLIGHTCONTROL:SetLimitLanding(Nlanding,Ntakeoff) -self.NlandingTot=Nlanding or 2 -self.NlandingTakeoff=Ntakeoff or 0 -return self -end -function FLIGHTCONTROL:SetLandingInterval(dt) -self.dTlanding=dt or 180 -return self -end -function FLIGHTCONTROL:SetLimitTaxi(Ntaxi,IncludeInbound,Nlanding) -self.NtaxiTot=Ntaxi or 2 -self.NtaxiInbound=IncludeInbound -self.NtaxiLanding=Nlanding or 0 -return self -end -function FLIGHTCONTROL:AddHoldingPattern(ArrivalZone,Heading,Length,FlightlevelMin,FlightlevelMax,Prio) -if type(ArrivalZone)=="string"then -ArrivalZone=ZONE:New(ArrivalZone) -end -self.hpcounter=self.hpcounter+1 -local hp={} -hp.uid=self.hpcounter -hp.arrivalzone=ArrivalZone -hp.name=string.format("%s-%d",ArrivalZone:GetName(),hp.uid) -hp.pos0=ArrivalZone:GetCoordinate() -hp.pos1=hp.pos0:Translate(UTILS.NMToMeters(Length or 15),Heading) -hp.angelsmin=FlightlevelMin or 5 -hp.angelsmax=FlightlevelMax or 15 -hp.prio=Prio or 50 -hp.stacks={} -for i=hp.angelsmin,hp.angelsmax do -local stack={} -stack.angels=i -stack.flightgroup=nil -stack.pos0=UTILS.DeepCopy(hp.pos0) -stack.pos0:SetAltitude(UTILS.FeetToMeters(i*1000)) -stack.pos1=UTILS.DeepCopy(hp.pos1) -stack.pos1:SetAltitude(UTILS.FeetToMeters(i*1000)) -stack.heading=Heading -table.insert(hp.stacks,stack) -end -table.insert(self.holdingpatterns,hp) -local function _sort(a,b) -return a.prio0 then -local text=string.format("Still got %d messages in the radio queue. Will call status again in %.1f sec",#self.msrsqueue,Tqueue) -self:T(self.lid..text) -self:__StatusUpdate(-Tqueue) -return false -end -return true -end -function FLIGHTCONTROL:onafterStatusUpdate() -self:T2(self.lid.."Status update") -self:_CheckMarkHoldingPatterns() -if self:IsRunwayOperational()==false then -local Trepair=self:GetRunwayRepairtime() -self:I(self.lid..string.format("Runway still destroyed! Will be repaired in %d sec",Trepair)) -if Trepair==0 then -self:RunwayRepaired() -end -end -self:_CheckFlights() -self:_CheckQueues() -local rwyLanding=self:GetActiveRunwayText() -local rwyTakeoff=self:GetActiveRunwayText(true) -local Nflights=self:CountFlights() -local NQparking=self:CountFlights(FLIGHTCONTROL.FlightStatus.PARKING) -local NQreadytx=self:CountFlights(FLIGHTCONTROL.FlightStatus.READYTX) -local NQtaxiout=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIOUT) -local NQreadyto=self:CountFlights(FLIGHTCONTROL.FlightStatus.READYTO) -local NQtakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF) -local NQinbound=self:CountFlights(FLIGHTCONTROL.FlightStatus.INBOUND) -local NQholding=self:CountFlights(FLIGHTCONTROL.FlightStatus.HOLDING) -local NQlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) -local NQtaxiinb=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIINB) -local NQarrived=self:CountFlights(FLIGHTCONTROL.FlightStatus.ARRIVED) -local Nqueues=(NQparking+NQreadytx+NQtaxiout+NQreadyto+NQtakeoff)+(NQinbound+NQholding+NQlanding+NQtaxiinb+NQarrived) -local nfree=self.Nparkingspots-NQarrived-NQparking -local Nfree=self:CountParking(AIRBASE.SpotStatus.FREE) -local Noccu=self:CountParking(AIRBASE.SpotStatus.OCCUPIED) -local Nresv=self:CountParking(AIRBASE.SpotStatus.RESERVED) -if Nfree+Noccu+Nresv~=self.Nparkingspots then -self:E(self.lid..string.format("WARNING: Number of parking spots does not match! Nfree=%d, Noccu=%d, Nreserved=%d != %d total",Nfree,Noccu,Nresv,self.Nparkingspots)) -end -if self.verbose>=1 then -local text=string.format("State %s - Runway Landing=%s, Takeoff=%s - Parking F=%d/O=%d/R=%d of %d - Flights=%s: Qpark=%d Qtxout=%d Qready=%d Qto=%d | Qinbound=%d Qhold=%d Qland=%d Qtxinb=%d Qarr=%d", -self:GetState(),rwyLanding,rwyTakeoff,Nfree,Noccu,Nresv,self.Nparkingspots,Nflights,NQparking,NQtaxiout,NQreadyto,NQtakeoff,NQinbound,NQholding,NQlanding,NQtaxiinb,NQarrived) -self:I(self.lid..text) -end -if Nflights==Nqueues then -else -self:E(string.format("WARNING: Number of total flights %d!=%d number of flights in all queues!",Nflights,Nqueues)) -end -if self.verbose>=2 then -local text="Holding Patterns:" -for i,_pattern in pairs(self.holdingpatterns)do -local pattern=_pattern -text=text..string.format("\n[%d] Pattern %s [Prio=%d, UID=%d]: Stacks=%d, Angels %d - %d",i,pattern.name,pattern.prio,pattern.uid,#pattern.stacks,pattern.angelsmin,pattern.angelsmax) -if self.verbose>=4 then -for _,_stack in pairs(pattern.stacks)do -local stack=_stack -local text=string.format("",stack.angels,stack) -end -end -end -self:I(self.lid..text) -end -self:__StatusUpdate(-30) -end -function FLIGHTCONTROL:onafterStop() -self:UnHandleEvent(EVENTS.Birth) -self:UnHandleEvent(EVENTS.EngineStartup) -self:UnHandleEvent(EVENTS.Takeoff) -self:UnHandleEvent(EVENTS.Land) -self:UnHandleEvent(EVENTS.EngineShutdown) -self:UnHandleEvent(EVENTS.Crash) -self:UnHandleEvent(EVENTS.Kill) -end -function FLIGHTCONTROL:OnEventBirth(EventData) -self:F3({EvendData=EventData}) -if EventData and EventData.IniGroupName and EventData.IniUnit then -self:T3(self.lid..string.format("BIRTH: unit = %s",tostring(EventData.IniUnitName))) -self:T3(self.lid..string.format("BIRTH: group = %s",tostring(EventData.IniGroupName))) -local unit=EventData.IniUnit -if unit:IsAir()then -local bornhere=EventData.Place and EventData.Place:GetName()==self.airbasename or false -local playerunit,playername=self:_GetPlayerUnitAndName(EventData.IniUnitName) -if playername or bornhere then -self:ScheduleOnce(0.5,self._CreateFlightGroup,self,EventData.IniGroup) -end -if bornhere then -self:SpawnParkingGuard(unit) -end -end -end -end -function FLIGHTCONTROL:OnEventCrashOrDead(EventData) -if EventData then -if EventData.IniUnitName then -if self.airbase and self.airbasename and self.airbasename==EventData.IniUnitName then -self:RunwayDestroyed() -end -end -end -end -function FLIGHTCONTROL:OnEventLand(EventData) -self:F3({EvendData=EventData}) -self:T2(self.lid..string.format("LAND: unit = %s",tostring(EventData.IniUnitName))) -self:T3(self.lid..string.format("LAND: group = %s",tostring(EventData.IniGroupName))) -end -function FLIGHTCONTROL:OnEventTakeoff(EventData) -self:F3({EvendData=EventData}) -self:T2(self.lid..string.format("TAKEOFF: unit = %s",tostring(EventData.IniUnitName))) -self:T3(self.lid..string.format("TAKEOFF: group = %s",tostring(EventData.IniGroupName))) -local airbase=EventData.Place -local unit=EventData.IniUnit -if not(airbase or unit)then -self:E(self.lid.."WARNING: Airbase or IniUnit is nil in takeoff event!") -return -end -end -function FLIGHTCONTROL:OnEventEngineStartup(EventData) -self:F3({EvendData=EventData}) -self:T2(self.lid..string.format("ENGINESTARTUP: unit = %s",tostring(EventData.IniUnitName))) -self:T3(self.lid..string.format("ENGINESTARTUP: group = %s",tostring(EventData.IniGroupName))) -end -function FLIGHTCONTROL:OnEventEngineShutdown(EventData) -self:F3({EvendData=EventData}) -self:T2(self.lid..string.format("ENGINESHUTDOWN: unit = %s",tostring(EventData.IniUnitName))) -self:T3(self.lid..string.format("ENGINESHUTDOWN: group = %s",tostring(EventData.IniGroupName))) -end -function FLIGHTCONTROL:OnEventKill(EventData) -self:F3({EvendData=EventData}) -self:T2(self.lid..string.format("KILL: ini unit = %s",tostring(EventData.IniUnitName))) -self:T3(self.lid..string.format("KILL: ini group = %s",tostring(EventData.IniGroupName))) -self:T2(self.lid..string.format("KILL: tgt unit = %s",tostring(EventData.TgtUnitName))) -self:T3(self.lid..string.format("KILL: tgt group = %s",tostring(EventData.TgtGroupName))) -local guardPrefix=string.format("Parking Guard %s",self.airbasename) -local victimName=EventData.IniUnitName -local killerName=EventData.TgtUnitName -if victimName and victimName:find(guardPrefix)then -env.info(string.format("Parking guard %s killed!",victimName)) -for _,_flight in pairs(self.flights)do -local flight=_flight -local element=flight:GetElementByName(killerName) -if element then -env.info(string.format("Parking guard %s killed by %s!",victimName,killerName)) -return -end -end -end -end -function FLIGHTCONTROL:onafterRunwayDestroyed(From,Event,To) -self:T(self.lid..string.format("Runway destoyed!")) -self.runwaydestroyed=timer.getAbsTime() -self:TransmissionTower("All flights, our runway was destroyed. All operations are suspended for one hour.",Flight,Delay) -end -function FLIGHTCONTROL:onafterRunwayRepaired(From,Event,To) -self:T(self.lid..string.format("Runway repaired!")) -self.runwaydestroyed=nil -end -function FLIGHTCONTROL:_CheckQueues() -if self.verbose>=2 then -self:_PrintQueue(self.flights,"All flights") -end -local flight,isholding,parking=self:_GetNextFlight() -if flight then -if isholding then -if self:_CheckFlightLanding(flight)then -local dTlanding=99999 -if self.Tlanding then -dTlanding=timer.getAbsTime()-self.Tlanding -end -if parking and dTlanding>=self.dTlanding then -local callsign=self:_GetCallsignName(flight) -local runway=self:GetActiveRunwayText() -local text=string.format("%s, %s, you are cleared to land, runway %s",callsign,self.alias,runway) -self:TransmissionTower(text,flight) -if flight.isAI then -local text=string.format("Runway %s, cleared to land, %s",runway,callsign) -self:TransmissionPilot(text,flight,10) -self:_LandAI(flight,parking) -else -self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.LANDING) -end -self.Tlanding=timer.getAbsTime() -end -else -self:T3(self.lid..string.format("FYI: Landing clearance for flight %s denied",flight.groupname)) -end -else -if self:_CheckFlightTakeoff(flight)then -local callsign=self:_GetCallsignName(flight) -local runway=self:GetActiveRunwayText(true) -local text=string.format("%s, %s, taxi to runway %s, hold short",callsign,self.alias,runway) -if self:GetFlightStatus(flight)==FLIGHTCONTROL.FlightStatus.READYTO then -text=string.format("%s, %s, cleared for take-off, runway %s",callsign,self.alias,runway) -end -self:TransmissionTower(text,flight) -if flight.isAI then -local text="Wilco, " -if flight:IsUncontrolled()then -text=text..string.format("starting engines, ") -flight:StartUncontrolled() -end -text=text..string.format("runway %s, %s",runway,callsign) -self:TransmissionPilot(text,flight,10) -for _,_element in pairs(flight.elements)do -local element=_element -if element and element.parking then -local spot=self:GetParkingSpotByID(element.parking.TerminalID) -self:RemoveParkingGuard(spot) -end -end -self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAKEOFF) -else -if self:GetFlightStatus(flight)==FLIGHTCONTROL.FlightStatus.READYTO then -self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAKEOFF) -else -for _,_element in pairs(flight.elements)do -local element=_element -if element.parking then -local spot=self:GetParkingSpotByID(element.parking.TerminalID) -if element.ai then -self:RemoveParkingGuard(spot,15) -else -self:RemoveParkingGuard(spot,10) -end -end -end -end -end -else -self:T3(self.lid..string.format("FYI: Take off for flight %s denied",flight.groupname)) -end -end -else -self:T2(self.lid..string.format("FYI: No flight in queue for takeoff or landing")) -end -end -function FLIGHTCONTROL:_CheckFlightTakeoff(flight) -local nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) -local ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF,nil,true) -local status=self:GetFlightStatus(flight) -if flight.isAI then -if nlanding>self.NtaxiLanding then -self:T(self.lid..string.format("AI flight %s [status=%s] NOT cleared for taxi/takeoff as %d>%d flight(s) landing",flight.groupname,status,nlanding,self.NtaxiLanding)) -return false -end -local ninbound=0 -if self.NtaxiInbound then -ninbound=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIINB,nil,true) -end -if ntakeoff+ninbound>=self.NtaxiTot then -self:T(self.lid..string.format("AI flight %s [status=%s] NOT cleared for taxi/takeoff as %d>=%d flight(s) taxi/takeoff",flight.groupname,status,ntakeoff,self.NtaxiTot)) -return false -end -self:T(self.lid..string.format("AI flight %s [status=%s] cleared for taxi/takeoff! nLanding=%d, nTakeoff=%d",flight.groupname,status,nlanding,ntakeoff)) -return true -else -if status==FLIGHTCONTROL.FlightStatus.READYTO then -if nlanding>self.NtaxiLanding then -self:T(self.lid..string.format("Player flight %s [status=%s] not cleared for taxi/takeoff as %d>%d flight(s) landing",flight.groupname,status,nlanding,self.NtaxiLanding)) -return false -end -end -self:T(self.lid..string.format("Player flight %s [status=%s] cleared for taxi/takeoff",flight.groupname,status)) -return true -end -end -function FLIGHTCONTROL:_CheckFlightLanding(flight) -local nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) -local ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF,nil,true) -local status=self:GetFlightStatus(flight) -if flight.isAi then -if ntakeoff<=self.NlandingTakeoff and nlandingTholdingMin then -return fg -end -end -local function _sortByFuel(a,b) -local flightA=a -local flightB=b -local fuelA=flightA.group:GetFuelMin() -local fuelB=flightB.group:GetFuelMin() -return fuelATholdingMin then -return fg -end -return nil -end -function FLIGHTCONTROL:_GetNextFightParking() -local OnlyAI=nil -if self:IsRunwayDestroyed()then -OnlyAI=false -end -local QreadyTO=self:GetFlights(FLIGHTCONTROL.FlightStatus.READYTO,OPSGROUP.GroupStatus.TAXIING,OnlyAI) -if#QreadyTO>0 then -return QreadyTO[1] -end -local QreadyTX=self:GetFlights(FLIGHTCONTROL.FlightStatus.READYTX,OPSGROUP.GroupStatus.PARKING,OnlyAI) -if#QreadyTX>0 then -return QreadyTX[1] -end -if self:IsRunwayDestroyed()then -return nil -end -local Qparking=self:GetFlights(FLIGHTCONTROL.FlightStatus.PARKING,nil,true) -local Nparking=#Qparking -if Nparking==0 then -return nil -end -local function _sortByTparking(a,b) -local flightA=a -local flightB=b -return flightA.Tparking=2 then -local text="Parking flights:" -for i,_flight in pairs(Qparking)do -local flight=_flight -text=text..string.format("\n[%d] %s [%s], state=%s [%s]: Tparking=%.1f sec",i,flight.groupname,flight.actype,flight:GetState(),self:GetFlightStatus(flight),flight:GetParkingTime()) -end -self:I(self.lid..text) -end -for i,_flight in pairs(Qparking)do -local flight=_flight -if flight.isAI and flight.isReadyTO then -return flight -end -end -return nil -end -function FLIGHTCONTROL:_PrintQueue(queue,name) -local text=string.format("%s Queue N=%d:",name,#queue) -if#queue==0 then -text=text.." empty." -else -local time=timer.getAbsTime() -for i,_flight in ipairs(queue)do -local flight=_flight -local fuel=flight.group:GetFuelMin()*100 -local ai=tostring(flight.isAI) -local actype=tostring(flight.actype) -local holding=flight.Tholding and UTILS.SecondsToClock(time-flight.Tholding,true)or"X" -local parking=flight.Tparking and UTILS.SecondsToClock(time-flight.Tparking,true)or"X" -local holding=flight:GetHoldingTime() -if holding>=0 then -holding=UTILS.SecondsToClock(holding,true) -else -holding="X" -end -local parking=flight:GetParkingTime() -if parking>=0 then -parking=UTILS.SecondsToClock(parking,true) -else -parking="X" -end -local nunits=flight:CountElements() -local state=flight:GetState() -local status=self:GetFlightStatus(flight) -text=text..string.format("\n[%d] %s (%s*%d): status=%s | %s, ai=%s, fuel=%d, holding=%s, parking=%s", -i,flight.groupname,actype,nunits,state,status,ai,fuel,holding,parking) -for j,_element in pairs(flight.elements)do -local element=_element -local life=element.unit:GetLife() -local life0=element.unit:GetLife0() -local park=element.parking and tostring(element.parking.TerminalID)or"N/A" -text=text..string.format("\n (%d) %s (%s): status=%s, ai=%s, airborne=%s life=%d/%d spot=%s", -j,tostring(element.modex),element.name,tostring(element.status),tostring(element.ai),tostring(element.unit:InAir()),life,life0,park) -end -end -end -self:I(self.lid..text) -return text -end -function FLIGHTCONTROL:SetFlightStatus(flight,status) -self:T(self.lid..string.format("New status %s-->%s for flight %s",flight.controlstatus or"unknown",status,flight:GetName())) -if flight.controlstatus~=status and not flight.isAI then -self:T(self.lid.."Updating menu in 0.2 sec after flight status change") -flight:_UpdateMenu(0.2) -end -flight.controlstatus=status -end -function FLIGHTCONTROL:GetFlightStatus(flight) -if flight then -return flight.controlstatus or"unkonwn" -end -return"unknown" -end -function FLIGHTCONTROL:IsControlling(flight) -local is=flight.flightcontrol and flight.flightcontrol.airbasename==self.airbasename or false -return is -end -function FLIGHTCONTROL:_InQueue(queue,group) -local name=group:GetName() -for _,_flight in pairs(queue)do -local flight=_flight -if name==flight.groupname then -return true -end -end -return false -end -function FLIGHTCONTROL:GetFlights(Status,GroupStatus,AI) -if Status~=nil or GroupStatus~=nil or AI~=nil then -local flights={} -for _,_flight in pairs(self.flights)do -local flight=_flight -local status=self:GetFlightStatus(flight,Status) -if status==Status then -if AI==nil or AI==flight.isAI then -if GroupStatus==nil or GroupStatus==flight:GetState()then -table.insert(flights,flight) -end -end -end -end -return flights -else -return self.flights -end -end -function FLIGHTCONTROL:CountFlights(Status,GroupStatus,AI) -if Status~=nil or GroupStatus~=nil or AI~=nil then -local flights=self:GetFlights(Status,GroupStatus,AI) -return#flights -else -return#self.flights -end -end -function FLIGHTCONTROL:GetActiveRunway() -local rwy=self.airbase:GetActiveRunway() -return rwy -end -function FLIGHTCONTROL:GetActiveRunwayLanding() -local rwy=self.airbase:GetActiveRunwayLanding() -return rwy -end -function FLIGHTCONTROL:GetActiveRunwayTakeoff() -local rwy=self.airbase:GetActiveRunwayTakeoff() -return rwy -end -function FLIGHTCONTROL:GetActiveRunwayText(Takeoff) -local runway -if Takeoff then -runway=self:GetActiveRunwayTakeoff() -else -runway=self:GetActiveRunwayLanding() -end -local name=self.airbase:GetRunwayName(runway,true) -return name or"XX" -end -function FLIGHTCONTROL:_InitParkingSpots() -local parkingdata=self.airbase:GetParkingSpotsTable() -self.parking={} -self.Nparkingspots=0 -for _,_spot in pairs(parkingdata)do -local spot=_spot -local text=string.format("Parking ID=%d, Terminal=%d: Free=%s, Client=%s, Dist=%.1f",spot.TerminalID,spot.TerminalType,tostring(spot.Free),tostring(spot.ClientName),spot.DistToRwy) -self:T3(self.lid..text) -self.parking[spot.TerminalID]=spot -if spot.Free then -self:SetParkingFree(spot) -else -local unit=spot.Coordinate:FindClosestUnit(20) -if unit then -local unitname=unit and unit:GetName()or"unknown" -local isalive=unit:IsAlive() -if isalive then -self:SetParkingOccupied(spot,unitname) -self:SpawnParkingGuard(unit) -else -self:SetParkingFree(spot) -end -else -self:E(self.lid..string.format("ERROR: Parking spot is NOT FREE but no unit could be found there!")) -end -end -self.Nparkingspots=self.Nparkingspots+1 -end -end -function FLIGHTCONTROL:GetParkingSpotByID(TerminalID) -return self.parking[TerminalID] -end -function FLIGHTCONTROL:_UpdateSpotStatus(spot,status,unitname) -self:T2(self.lid..string.format("Updating parking spot %d status: %s --> %s (unit=%s)",spot.TerminalID,tostring(spot.Status),status,tostring(unitname))) -spot.Status=status -end -function FLIGHTCONTROL:SetParkingFree(spot) -local spot=self:GetParkingSpotByID(spot.TerminalID) -self:_UpdateSpotStatus(spot,AIRBASE.SpotStatus.FREE,spot.OccupiedBy or spot.ReservedBy) -spot.OccupiedBy=nil -spot.ReservedBy=nil -self:RemoveParkingGuard(spot) -self:UpdateParkingMarker(spot) -end -function FLIGHTCONTROL:SetParkingReserved(spot,unitname) -local spot=self:GetParkingSpotByID(spot.TerminalID) -self:_UpdateSpotStatus(spot,AIRBASE.SpotStatus.RESERVED,unitname) -spot.ReservedBy=unitname or"unknown" -self:UpdateParkingMarker(spot) -end -function FLIGHTCONTROL:SetParkingOccupied(spot,unitname) -local spot=self:GetParkingSpotByID(spot.TerminalID) -self:_UpdateSpotStatus(spot,AIRBASE.SpotStatus.OCCUPIED,unitname) -spot.OccupiedBy=unitname or"unknown" -self:UpdateParkingMarker(spot) -end -function FLIGHTCONTROL:UpdateParkingMarker(spot) -if self.markerParking then -local spot=self:GetParkingSpotByID(spot.TerminalID) -if spot.Status==AIRBASE.SpotStatus.FREE then -if spot.Marker then -spot.Marker:Remove() -end -else -local text=string.format("Spot %d (type %d): %s",spot.TerminalID,spot.TerminalType,spot.Status:upper()) -if spot.OccupiedBy then -text=text..string.format("\nOccupied by %s",tostring(spot.OccupiedBy)) -end -if spot.ReservedBy then -text=text..string.format("\nReserved for %s",tostring(spot.ReservedBy)) -end -if spot.ClientSpot then -text=text..string.format("\nClient %s",tostring(spot.ClientName)) -end -if spot.Marker then -if text~=spot.Marker.text or not spot.Marker.shown then -spot.Marker:UpdateText(text) -end -else -spot.Marker=MARKER:New(spot.Coordinate,text):ToAll() -end -end -end -end -function FLIGHTCONTROL:IsParkingFree(spot) -return spot.Status==AIRBASE.SpotStatus.FREE -end -function FLIGHTCONTROL:IsParkingOccupied(spot) -if spot.Status==AIRBASE.SpotStatus.OCCUPIED then -return tostring(spot.OccupiedBy) -else -return false -end -end -function FLIGHTCONTROL:IsParkingReserved(spot) -if spot.Status==AIRBASE.SpotStatus.RESERVED then -return tostring(spot.ReservedBy) -else -return false -end -end -function FLIGHTCONTROL:_GetFreeParkingSpots(terminal) -local freespots={} -local n=0 -for _,_parking in pairs(self.parking)do -local parking=_parking -if self:IsParkingFree(parking)then -if terminal==nil or terminal==parking.terminal then -n=n+1 -table.insert(freespots,parking) -end -end -end -return n,freespots -end -function FLIGHTCONTROL:GetClosestParkingSpot(Coordinate,TerminalType,Status) -local distmin=math.huge -local spotmin=nil -for TerminalID,Spot in pairs(self.parking)do -local spot=Spot -if(Status==nil or Status==spot.Status)and AIRBASE._CheckTerminalType(spot.TerminalType,TerminalType)then -local dist=Coordinate:Get2DDistance(spot.Coordinate) -if dist0 then -text=text..string.format("\n- Parking %d",NQparking) -end -if NQreadytx>0 then -text=text..string.format("\n- Ready to taxi %d",NQreadytx) -end -if NQtaxiout>0 then -text=text..string.format("\n- Taxi to runway %d",NQtaxiout) -end -if NQreadyto>0 then -text=text..string.format("\n- Ready for takeoff %d",NQreadyto) -end -if NQtakeoff>0 then -text=text..string.format("\n- Taking off %d",NQtakeoff) -end -if NQinbound>0 then -text=text..string.format("\n- Inbound %d",NQinbound) -end -if NQholding>0 then -text=text..string.format("\n- Holding pattern %d",NQholding) -end -if NQlanding>0 then -text=text..string.format("\n- Landing %d",NQlanding) -end -if NQtaxiinb>0 then -text=text..string.format("\n- Taxi to parking %d",NQtaxiinb) -end -if NQarrived>0 then -text=text..string.format("\n- Arrived at parking %d",NQarrived) -end -self:TextMessageToFlight(text,flight,15,true) -else -self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) -end -end -function FLIGHTCONTROL:_PlayerRequestInbound(groupname) -local flight=_DATABASE:GetOpsGroup(groupname) -if flight then -if flight:IsAirborne()then -local callsign=self:_GetCallsignName(flight) -local player=flight:GetPlayerElement() -local text=string.format("%s, %s, inbound for landing",self.alias,callsign) -self:TransmissionPilot(text,flight) -local flightcoord=flight:GetCoordinate(nil,player.name) -local dist=flightcoord:Get2DDistance(self:GetCoordinate()) -if distself.NlandingTakeoff then -local text=string.format("%s, negative! We have currently traffic taking off!",callsign) -self:TransmissionTower(text,flight,10) -else -local runway=self:GetActiveRunwayText() -local text=string.format("%s, affirmative, runway %s. Confirm approach!",callsign,runway) -self:TransmissionTower(text,flight,10) -self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.LANDING) -end -else -local text=string.format("Negative, you must be INBOUND and CONTROLLED by us!") -self:TextMessageToFlight(text,flight,10) -end -else -self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) -end -end -function FLIGHTCONTROL:_PlayerRequestTaxi(groupname) -local flight=_DATABASE:GetOpsGroup(groupname) -if flight then -local callsign=self:_GetCallsignName(flight) -local text=string.format("%s, %s, request taxi to runway.",self.alias,callsign) -self:TransmissionPilot(text,flight) -if flight:IsParking()then -local text=string.format("%s, %s, hold position until further notice.",callsign,self.alias) -self:TransmissionTower(text,flight,10) -self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.READYTX) -elseif flight:IsTaxiing()then -local runway=self:GetActiveRunwayText(true) -local text=string.format("%s, %s, taxi to runway %s, hold short.",callsign,self.alias,runway) -self:TransmissionTower(text,flight,10) -self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAXIOUT) -local playerElement=flight:GetPlayerElement() -if playerElement and playerElement.parking then -self:SetParkingFree(playerElement.parking) -end -else -self:TextMessageToFlight(string.format("Negative, you must be PARKING to request TAXI!"),flight) -end -else -self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) -end -end -function FLIGHTCONTROL:_PlayerAbortTaxi(groupname) -local flight=_DATABASE:GetOpsGroup(groupname) -if flight then -local callsign=self:_GetCallsignName(flight) -local text=string.format("%s, %s, cancel my taxi request.",self.alias,callsign) -self:TransmissionPilot(text,flight) -if flight:IsParking()then -local text=string.format("%s, %s, roger, remain on your parking position.",callsign,self.alias) -self:TransmissionTower(text,flight,10) -self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.PARKING) -local playerElement=flight:GetPlayerElement() -if playerElement then -self:SpawnParkingGuard(playerElement.unit) -end -elseif flight:IsTaxiing()then -local text=string.format("%s, %s, roger, return to your parking position.",callsign,self.alias) -self:TransmissionTower(text,flight,10) -self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAXIINB) -else -self:TextMessageToFlight(string.format("Negative, you must be PARKING or TAXIING to abort TAXI!"),flight) -end -else -self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) -end -end -function FLIGHTCONTROL:_PlayerRequestTakeoff(groupname) -local flight=_DATABASE:GetOpsGroup(groupname) -if flight then -if flight:IsTaxiing()then -local callsign=self:_GetCallsignName(flight) -local text=string.format("%s, %s, ready for departure. Request takeoff.",self.alias,callsign) -self:TransmissionPilot(text,flight) -local Nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) -local Ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF) -local text=string.format("%s, %s, ",callsign,self.alias) -if Nlanding==0 then -text=text.."no current traffic. You are cleared for takeoff." -self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAKEOFF) -elseif Nlanding>0 then -if Nlanding==1 then -text=text..string.format("negative, we got %d flight inbound before it's your turn. Hold position until futher notice.",Nlanding) -else -text=text..string.format("negative, we got %d flights inbound. Hold positon until futher notice.",Nlanding) -end -end -self:TransmissionTower(text,flight,10) -else -self:TextMessageToFlight(string.format("Negative, you must request TAXI before you can request TAKEOFF!"),flight) -end -else -self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) -end -end -function FLIGHTCONTROL:_PlayerAbortTakeoff(groupname) -local flight=_DATABASE:GetOpsGroup(groupname) -if flight then -local status=self:GetFlightStatus(flight) -if status==FLIGHTCONTROL.FlightStatus.TAKEOFF or status==FLIGHTCONTROL.FlightStatus.READYTO then -local callsign=self:_GetCallsignName(flight) -local text=string.format("%s, %s, abort takeoff.",self.alias,callsign) -self:TransmissionPilot(text,flight) -if flight:IsParking()then -text=string.format("%s, %s, affirm, remain on your parking position.",callsign,self.alias) -self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.PARKING) -local playerElement=flight:GetPlayerElement() -if playerElement then -self:SpawnParkingGuard(playerElement.unit) -end -elseif flight:IsTaxiing()then -text=string.format("%s, %s, roger, report whether you want to taxi back or takeoff later.",callsign,self.alias) -self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAXIOUT) -else -env.info(self.lid.."ERROR") -end -self:TransmissionTower(text,flight,10) -else -self:TextMessageToFlight("Negative, You are NOT in the takeoff queue",flight) -end -else -self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) -end -end -function FLIGHTCONTROL:_PlayerRequestParking(groupname) -local flight=_DATABASE:GetOpsGroup(groupname) -if flight then -local callsign=self:_GetCallsignName(flight) -local player=flight:GetPlayerElement() -local TerminalType=AIRBASE.TerminalType.FighterAircraft -if flight.isHelo then -TerminalType=AIRBASE.TerminalType.HelicopterUsable -end -local coord=flight:GetCoordinate(nil,player.name) -local spot=self:_GetPlayerSpot(player.name) -if not spot then -spot=self:GetClosestParkingSpot(coord,TerminalType,AIRBASE.SpotStatus.FREE) -end -if spot then -local text=string.format("%s, your assigned parking position is terminal ID %d.",callsign,spot.TerminalID) -self:TransmissionTower(text,flight) -if player.parking then -self:SetParkingFree(player.parking) -end -player.parking=spot -self:SetParkingReserved(spot,player.name) -flight:_UpdateMenu(0.2) -else -local text=string.format("%s, no free parking spot available. Try again later.",callsign) -self:TransmissionTower(text,flight) -end -else -self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) -end -end -function FLIGHTCONTROL:_PlayerCancelParking(groupname) -local flight=_DATABASE:GetOpsGroup(groupname) -if flight then -local callsign=self:_GetCallsignName(flight) -local player=flight:GetPlayerElement() -if player.parking then -self:SetParkingFree(player.parking) -player.parking=nil -self:TextMessageToFlight(string.format("%s, your parking spot reservation at terminal ID %d was cancelled.",callsign,player.parking.TerminalID),flight) -else -self:TextMessageToFlight("You did not have a valid parking spot reservation.",flight) -end -flight:_UpdateMenu(0.2) -else -self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) -end -end -function FLIGHTCONTROL:_PlayerArrived(groupname) -local flight=_DATABASE:GetOpsGroup(groupname) -if flight then -local player=flight:GetPlayerElement() -local coord=flight:GetCoordinate(nil,player.name) -local spot=self:_GetPlayerSpot(player.name) -if player.parking then -spot=self:GetParkingSpotByID(player.parking.TerminalID) -else -if not spot then -spot=self:GetClosestParkingSpot(coord) -end -end -if spot then -local callsign=self:_GetCallsignName(flight) -local dist=coord:Get2DDistance(spot.Coordinate) -if dist<12 then -local text=string.format("%s, %s, arrived at parking position. Terminal ID %d.",self.alias,callsign,spot.TerminalID) -self:TransmissionPilot(text,flight) -local text="" -if spot.ReservedBy and spot.ReservedBy~=player.name then -text=string.format("%s, this spot is already reserved for %s. Find yourself a different parking position.",callsign,self.alias,spot.ReservedBy) -else -text=string.format("%s, %s, roger. Enjoy a cool bevarage in the officers' club.",callsign,self.alias) -flight:ElementParking(player,spot) -self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.PARKING) -if player then -self:SpawnParkingGuard(player.unit) -end -end -self:TransmissionTower(text,flight,10) -else -local text=string.format("%s, %s, arrived at parking position.",self.alias,callsign) -self:TransmissionPilot(text,flight) -local text="" -if spot.ReservedBy then -if spot.ReservedBy==player.name then -text=string.format("%s, %s, you are still %d meters away from your reserved parking position at terminal ID %d. Continue taxiing!",callsign,self.alias,dist,spot.TerminalID) -else -text=string.format("%s, %s, the closest parking spot is already reserved. Continue taxiing to a free spot!",callsign,self.alias) -end -else -text=string.format("%s, %s, you are still %d meters away from the closest parking position. Continue taxiing to a proper spot!",callsign,self.alias,dist) -end -self:TransmissionTower(text,flight,10) -end -else -end -else -self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) -end -end -function FLIGHTCONTROL:_CreateFlightGroup(group) -if self:_InQueue(self.flights,group)then -self:E(self.lid..string.format("WARNING: Flight group %s does already exist!",group:GetName())) -return -end -self:T(self.lid..string.format("Creating new flight for group %s of aircraft type %s.",group:GetName(),group:GetTypeName())) -local flight=_DATABASE:GetOpsGroup(group:GetName()) -if not flight then -flight=FLIGHTGROUP:New(group:GetName()) -end -if flight.homebase and flight.homebase:GetName()==self.airbasename then -flight:SetFlightControl(self) -end -return flight -end -function FLIGHTCONTROL:_RemoveFlight(Flight) -for i,_flight in pairs(self.flights)do -local flight=_flight -if flight.groupname==Flight.groupname then -self:T(self.lid..string.format("Removing flight group %s",flight.groupname)) -table.remove(self.flights,i) -Flight.flightcontrol=nil -self:SetFlightStatus(Flight,FLIGHTCONTROL.FlightStatus.UNKNOWN) -return true -end -end -self:E(self.lid..string.format("WARNING: Could NOT remove flight group %s",Flight.groupname)) -end -function FLIGHTCONTROL:_GetFlightFromGroup(group) -if group then -local name=group:GetName() -for i,_flight in pairs(self.flights)do -local flight=_flight -if flight.groupname==name then -return flight,i -end -end -self:T2(self.lid..string.format("WARNING: Flight group %s could not be found in queue.",name)) -end -self:T2(self.lid..string.format("WARNING: Flight group could not be found in queue. Group is nil!")) -return nil,nil -end -function FLIGHTCONTROL:_GetFlightElement(unitname) -local unit=UNIT:FindByName(unitname) -if unit then -local flight=self:_GetFlightFromGroup(unit:GetGroup()) -if flight then -for i,_element in pairs(flight.elements)do -local element=_element -if element.unit:GetName()==unitname then -return element,i,flight -end -end -self:T2(self.lid..string.format("WARNING: Flight element %s could not be found in flight group.",unitname,flight.groupname)) -end -end -return nil,nil,nil -end -function FLIGHTCONTROL:_CheckFlights() -for i=#self.flights,1,-1 do -local flight=self.flights[i] -if flight:IsDead()then -self:T(self.lid..string.format("Removing DEAD flight %s",tostring(flight.groupname))) -self:_RemoveFlight(flight) -end -end -if self.speedLimitTaxi then -for _,_flight in pairs(self.flights)do -local flight=_flight -if not flight.isAI then -local playerElement=flight:GetPlayerElement() -local flightstatus=self:GetFlightStatus(flight) -if playerElement then -if(flightstatus==FLIGHTCONTROL.FlightStatus.TAXIINB or flightstatus==FLIGHTCONTROL.FlightStatus.TAXIOUT)and self.speedLimitTaxi then -local speed=playerElement.unit:GetVelocityMPS() -local coord=playerElement.unit:GetCoord() -local onRunway=self:IsCoordinateRunway(coord) -self:T(self.lid..string.format("Player %s speed %.1f knots (max=%.1f) onRunway=%s",playerElement.playerName,UTILS.MpsToKnots(speed),UTILS.MpsToKnots(self.speedLimitTaxi),tostring(onRunway))) -if speed and speed>self.speedLimitTaxi and not onRunway then -local callsign=self:_GetCallsignName(flight) -local text=string.format("%s, slow down, you are taxiing too fast!",callsign) -self:TransmissionTower(text,flight) -local PlayerData=flight:_GetPlayerData() -self:PlayerSpeeding(PlayerData) -end -end -end -end -end -end -end -function FLIGHTCONTROL:_CheckParking() -for TerminalID,_spot in pairs(self.parking)do -local spot=_spot -if spot.Reserved then -if spot.MarkerID then -spot.Coordinate:RemoveMark(spot.MarkerID) -end -spot.MarkerID=spot.Coordinate:MarkToCoalition(string.format("Parking reserved for %s",tostring(spot.Reserved)),self:GetCoalition()) -end -for i=1,#self.flights do -local flight=self.flights[i] -for _,_element in pairs(flight.elements)do -local element=_element -if element.parking and element.parking.TerminalID==TerminalID then -if spot.MarkerID then -spot.Coordinate:RemoveMark(spot.MarkerID) -end -spot.MarkerID=spot.Coordinate:MarkToCoalition(string.format("Parking spot occupied by %s",tostring(element.name)),self:GetCoalition()) -end -end -end -end -end -function FLIGHTCONTROL:_LandAI(flight,parking) -self:T(self.lid..string.format("Landing AI flight %s.",flight.groupname)) -local respawn=false -if respawn then -local Template=flight.group:GetTemplate() -Template.route.points=wp -for i,unit in pairs(Template.units)do -local spot=parking[i] -local element=flight:GetElementByName(unit.name) -if element then -unit.parking_landing=spot.TerminalID -local text=string.format("Reserving parking spot %d for unit %s",spot.TerminalID,tostring(unit.name)) -self:T(self.lid..text) -self:SetParkingReserved(spot,element.name) -else -env.info("FF error could not get element to assign parking!") -end -end -self:TextMessageToFlight(string.format("Respawning group %s",flight.groupname),flight) -flight:Respawn(Template) -else -flight:ClearToLand() -end -end -function FLIGHTCONTROL:_GetHoldingStack(flight) -self:T(self.lid..string.format("Getting holding point for flight %s",flight:GetName())) -for i,_hp in pairs(self.holdingpatterns)do -local holdingpattern=_hp -self:T(self.lid..string.format("Checking holding point %s",holdingpattern.name)) -for j,_stack in pairs(holdingpattern.stacks)do -local stack=_stack -local name=stack.flightgroup and stack.flightgroup:GetName()or"empty" -self:T(self.lid..string.format("Stack %d: %s",j,name)) -if not stack.flightgroup then -return stack -end -end -end -return nil -end -function FLIGHTCONTROL:_CountFlightsInPattern(Pattern) -local N=0 -for _,_stack in pairs(Pattern.stacks)do -local stack=_stack -if stack.flightgroup then -N=N+1 -end -end -return N -end -function FLIGHTCONTROL:_FlightOnFinal(flight) -local callsign=self:_GetCallsignName(flight) -local text=string.format("%s, final",callsign) -self:TransmissionPilot(text,flight) -return self -end -function FLIGHTCONTROL:TransmissionTower(Text,Flight,Delay) -local text=self:_GetTextForSpeech(Text) -local subgroups=nil -if Flight and not Flight.isAI then -local playerData=Flight:_GetPlayerData() -if playerData.subtitles and(not self.nosubs)then -subgroups=subgroups or{} -table.insert(subgroups,Flight.group) -end -end -local transmission=self.msrsqueue:NewTransmission(text,nil,self.msrsTower,nil,1,subgroups,Text) -self.Tlastmessage=timer.getAbsTime()+(Delay or 0) -self:T(self.lid..string.format("Radio Tower: %s",Text)) -end -function FLIGHTCONTROL:TransmissionPilot(Text,Flight,Delay) -local playerData=Flight:_GetPlayerData() -if playerData==nil or playerData.myvoice then -local text=self:_GetTextForSpeech(Text) -local msrs=self.msrsPilot -if Flight.useSRS and Flight.msrs then -msrs=Flight.msrs -end -local subgroups=nil -if Flight and not Flight.isAI then -local playerData=Flight:_GetPlayerData() -if playerData.subtitles and(not self.nosubs)then -subgroups=subgroups or{} -table.insert(subgroups,Flight.group) -end -end -local coordinate=Flight:GetCoordinate(true) -msrs:SetCoordinate() -self.msrsqueue:NewTransmission(text,nil,msrs,nil,1,subgroups,Text,nil,self.frequency,self.modulation) -end -self.Tlastmessage=timer.getAbsTime()+(Delay or 0) -self:T(self.lid..string.format("Radio Pilot: %s",Text)) -end -function FLIGHTCONTROL:TextMessageToFlight(Text,Flight,Duration,Clear,Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,FLIGHTCONTROL.TextMessageToFlight,self,Text,Flight,Duration,Clear,0) -else -if Flight and Flight.group and Flight.group:IsAlive()then -local gid=Flight.group:GetID() -trigger.action.outTextForGroup(gid,self:_CleanText(Text),Duration or 5,Clear) -end -end -end -function FLIGHTCONTROL:_CleanText(Text) -local text=Text:gsub("\n$",""):gsub("\n$","") -return text -end -function FLIGHTCONTROL:_SpawnParkingGuard(unit) -local coordinate=unit:GetCoordinate() -local spot=self:GetClosestParkingSpot(coordinate) -if not spot.ParkingGuard then -local heading=unit:GetHeading() -local size,x,y,z=unit:GetObjectSize() -local xdiff=3 -if AIRBASE._CheckTerminalType(spot.TerminalType,AIRBASE.TerminalType.Shelter)then -xdiff=27-(x*0.5) -end -if(AIRBASE._CheckTerminalType(spot.TerminalType,AIRBASE.TerminalType.OpenMed)or AIRBASE._CheckTerminalType(spot.TerminalType,AIRBASE.TerminalType.Shelter))and self.airbasename==AIRBASE.Sinai.Ramon_Airbase then -xdiff=12 -end -self:T2(self.lid..string.format("Parking guard for %s: heading=%d, length x=%.1f m, xdiff=%.1f m",unit:GetName(),heading,x,xdiff)) -local Coordinate=coordinate:Translate(x*0.5+xdiff,heading) -local lookat=heading-180 -self.parkingGuard:InitHeading(lookat) -if self.parkingGuard:IsInstanceOf("SPAWN")then -end -spot.ParkingGuard=self.parkingGuard:SpawnFromCoordinate(Coordinate) -else -self:E(self.lid.."ERROR: Parking Guard already exists!") -end -end -function FLIGHTCONTROL:SpawnParkingGuard(unit) -if unit and self.parkingGuard then -self:ScheduleOnce(1,FLIGHTCONTROL._SpawnParkingGuard,self,unit) -end -end -function FLIGHTCONTROL:RemoveParkingGuard(spot,delay) -if delay and delay>0 then -self:ScheduleOnce(delay,FLIGHTCONTROL.RemoveParkingGuard,self,spot) -else -if spot.ParkingGuard then -spot.ParkingGuard:Destroy() -spot.ParkingGuard=nil -end -end -end -function FLIGHTCONTROL:_IsFlightOnRunway(flight) -for _,_runway in pairs(self.airbase.runways)do -local runway=_runway -local inzone=flight:IsInZone(runway.zone) -if inzone then -return runway -end -end -return nil -end -function FLIGHTCONTROL:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) -if not ShortCallsign or ShortCallsign==false then -self.ShortCallsign=false -else -self.ShortCallsign=true -end -self.Keepnumber=Keepnumber or false -self.CallsignTranslations=CallsignTranslations -return self -end -function FLIGHTCONTROL:_GetCallsignName(flight) -local callsign=flight:GetCallsignName(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) -return callsign -end -function FLIGHTCONTROL:_GetTextForSpeech(text) -local function space(text) -local res="" -for i=1,#text do -local char=text:sub(i,i) -res=res..char.." " -end -return res -end -local t=text:gsub("(%d+)",space) -return t -end -function FLIGHTCONTROL:_GetPlayerUnitAndName(unitName) -if unitName then -local DCSunit=Unit.getByName(unitName) -if DCSunit then -local playername=DCSunit:getPlayerName() -local unit=UNIT:Find(DCSunit) -if DCSunit and unit and playername then -self:T(self.lid..string.format("Found DCS unit %s with player %s",tostring(unitName),tostring(playername))) -return unit,playername -end -end -end -return nil,nil -end -function FLIGHTCONTROL:_CheckMarkHoldingPatterns() -for _,pattern in pairs(self.holdingpatterns)do -local Pattern=pattern -if self.markPatterns then -self:_MarkHoldingPattern(Pattern) -else -self:_UnMarkHoldingPattern(Pattern) -end -end -end -function FLIGHTCONTROL:_MarkHoldingPattern(Pattern) -if not Pattern.markArrow then -Pattern.markArrow=Pattern.pos0:ArrowToAll(Pattern.pos1,nil,{1,0,0},1,{1,1,0},0.5,2,true) -end -if not Pattern.markArrival then -Pattern.markArrival=Pattern.arrivalzone:DrawZone() -end -end -function FLIGHTCONTROL:_UnMarkHoldingPattern(Pattern) -if Pattern.markArrow then -UTILS.RemoveMark(Pattern.markArrow) -Pattern.markArrow=nil -end -if Pattern.markArrival then -UTILS.RemoveMark(Pattern.markArrival) -Pattern.markArrival=nil -end -end -function FLIGHTCONTROL:_AddHoldingPatternBackup() -local runway=self:GetActiveRunway() -local heading=runway.heading -local vec2=self.airbase:GetVec2() -local Vec2=UTILS.Vec2Translate(vec2,UTILS.NMToMeters(5),heading+90) -local ArrivalZone=ZONE_RADIUS:New("Arrival Zone",Vec2,5000) -self.holdingBackup=self:AddHoldingPattern(ArrivalZone,heading,15,5,25,999) -return self -end -FLIGHTGROUP={ -ClassName="FLIGHTGROUP", -homebase=nil, -destbase=nil, -homezone=nil, -destzone=nil, -actype=nil, -speedMax=nil, -rangemax=nil, -ceiling=nil, -fuellow=false, -fuellowthresh=nil, -fuellowrtb=nil, -fuelcritical=nil, -fuelcriticalthresh=nil, -fuelcriticalrtb=false, -outofAAMrtb=false, -outofAGMrtb=false, -flightcontrol=nil, -flaghold=nil, -Tholding=nil, -Tparking=nil, -Twaiting=nil, -menu=nil, -isHelo=nil, -RTBRecallCount=0, -playerSettings={}, -playerWarnings={}, -prohibitAB=false, -jettisonEmptyTanks=true, -jettisonWeapons=true, -} -FLIGHTGROUP.Attribute={ -TRANSPORTPLANE="TransportPlane", -AWACS="AWACS", -FIGHTER="Fighter", -BOMBER="Bomber", -TANKER="Tanker", -TRANSPORTHELO="TransportHelo", -ATTACKHELO="AttackHelo", -UAV="UAV", -OTHER="Other", -} -FLIGHTGROUP.RadioMessage={ -AIRBORNE={normal="Airborn",enhanced="Airborn"}, -TAXIING={normal="Taxiing",enhanced="Taxiing"}, -} -FLIGHTGROUP.PlayerSkill={ -STUDENT="Student", -AVIATOR="Aviator", -GRADUATE="Graduate", -INSTRUCTOR="Instructor", -} -FLIGHTGROUP.Players={} -FLIGHTGROUP.version="1.0.2" -function FLIGHTGROUP:New(group) -local og=_DATABASE:GetOpsGroup(group) -if og then -og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) -return og -end -local self=BASE:Inherit(self,OPSGROUP:New(group)) -self.lid=string.format("FLIGHTGROUP %s | ",self.groupname) -self:SetDefaultROE() -self:SetDefaultROT() -self:SetDefaultEPLRS(self.isEPLRS) -self:SetDetection() -self:SetFuelLowThreshold() -self:SetFuelLowRTB() -self:SetFuelCriticalThreshold() -self:SetFuelCriticalRTB() -self.flaghold=USERFLAG:New(string.format("%s_FlagHold",self.groupname)) -self.flaghold:Set(0) -self:AddTransition("*","LandAtAirbase","Inbound") -self:AddTransition("*","RTB","Inbound") -self:AddTransition("*","RTZ","Inbound") -self:AddTransition("Inbound","Holding","Holding") -self:AddTransition("*","Refuel","Going4Fuel") -self:AddTransition("Going4Fuel","Refueled","Cruising") -self:AddTransition("*","LandAt","LandingAt") -self:AddTransition("LandingAt","LandedAt","LandedAt") -self:AddTransition("*","FuelLow","*") -self:AddTransition("*","FuelCritical","*") -self:AddTransition("Cruising","EngageTarget","Engaging") -self:AddTransition("Engaging","Disengage","Cruising") -self:AddTransition("*","ElementParking","*") -self:AddTransition("*","ElementEngineOn","*") -self:AddTransition("*","ElementTaxiing","*") -self:AddTransition("*","ElementTakeoff","*") -self:AddTransition("*","ElementAirborne","*") -self:AddTransition("*","ElementLanded","*") -self:AddTransition("*","ElementArrived","*") -self:AddTransition("*","ElementOutOfAmmo","*") -self:AddTransition("*","Parking","Parking") -self:AddTransition("*","Taxiing","Taxiing") -self:AddTransition("*","Takeoff","Airborne") -self:AddTransition("*","Airborne","Airborne") -self:AddTransition("*","Cruise","Cruising") -self:AddTransition("*","Landing","Landing") -self:AddTransition("*","Landed","Landed") -self:AddTransition("*","Arrived","Arrived") -self:HandleEvent(EVENTS.Birth,self.OnEventBirth) -self:HandleEvent(EVENTS.EngineStartup,self.OnEventEngineStartup) -self:HandleEvent(EVENTS.Takeoff,self.OnEventTakeOff) -self:HandleEvent(EVENTS.Land,self.OnEventLanding) -self:HandleEvent(EVENTS.EngineShutdown,self.OnEventEngineShutdown) -self:HandleEvent(EVENTS.PilotDead,self.OnEventPilotDead) -self:HandleEvent(EVENTS.Ejection,self.OnEventEjection) -self:HandleEvent(EVENTS.Crash,self.OnEventCrash) -self:HandleEvent(EVENTS.RemoveUnit,self.OnEventRemoveUnit) -self:HandleEvent(EVENTS.UnitLost,self.OnEventUnitLost) -self:HandleEvent(EVENTS.Kill,self.OnEventKill) -self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventPlayerLeaveUnit) -self:_InitGroup() -self:_InitWaypoints() -self.timerStatus=TIMER:New(self.Status,self):Start(1,30) -self.timerQueueUpdate=TIMER:New(self._QueueUpdate,self):Start(2,5) -self.timerCheckZone=TIMER:New(self._CheckInZones,self):Start(3,10) -_DATABASE:AddOpsGroup(self) -return self -end -function FLIGHTGROUP:AddTaskEnrouteEngageTargetsInZone(ZoneRadius,TargetTypes,Priority) -local Task=self.group:EnRouteTaskEngageTargetsInZone(ZoneRadius:GetVec2(),ZoneRadius:GetRadius(),TargetTypes,Priority) -self:AddTaskEnroute(Task) -end -function FLIGHTGROUP:GetAirwing() -return self.legion -end -function FLIGHTGROUP:GetAirwingName() -local name=self.legion and self.legion.alias or"None" -return name -end -function FLIGHTGROUP:GetSquadron() -return self.cohort -end -function FLIGHTGROUP:GetSquadronName() -local name=self.cohort and self.cohort:GetName()or"None" -return name -end -function FLIGHTGROUP:SetVTOL() -self.isVTOL=true -return self -end -function FLIGHTGROUP:SetProhibitAfterburner() -self.prohibitAB=true -if self:GetGroup():IsAlive()then -self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_AB,true) -end -return self -end -function FLIGHTGROUP:SetAllowAfterburner() -self.prohibitAB=false -if self:GetGroup():IsAlive()then -self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_AB,false) -end -return self -end -function FLIGHTGROUP:SetJettisonEmptyTanks(Switch) -self.jettisonEmptyTanks=Switch -if self:GetGroup():IsAlive()then -self:GetGroup():SetOption(AI.Option.Air.id.JETT_TANKS_IF_EMPTY,Switch) -end -return self -end -function FLIGHTGROUP:SetJettisonWeapons(Switch) -self.jettisonWeapons=not Switch -if self:GetGroup():IsAlive()then -self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_JETT,not Switch) -end -return self -end -function FLIGHTGROUP:SetReadyForTakeoff(ReadyTO,Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,FLIGHTGROUP.SetReadyForTakeoff,self,ReadyTO,0) -else -self.isReadyTO=ReadyTO -end -return self -end -function FLIGHTGROUP:SetFlightControl(flightcontrol) -if self.flightcontrol then -if self.flightcontrol:IsControlling(self)then -return -else -self.flightcontrol:_RemoveFlight(self) -end -end -self:T(self.lid..string.format("Setting FLIGHTCONTROL to airbase %s",flightcontrol.airbasename)) -self.flightcontrol=flightcontrol -if not flightcontrol:IsFlight(self)then -table.insert(flightcontrol.flights,self) -end -return self -end -function FLIGHTGROUP:GetFlightControl() -return self.flightcontrol -end -function FLIGHTGROUP:SetHomebase(HomeAirbase) -if type(HomeAirbase)=="string"then -HomeAirbase=AIRBASE:FindByName(HomeAirbase) -end -self.homebase=HomeAirbase -return self -end -function FLIGHTGROUP:SetDestinationbase(DestinationAirbase) -if type(DestinationAirbase)=="string"then -DestinationAirbase=AIRBASE:FindByName(DestinationAirbase) -end -self.destbase=DestinationAirbase -return self -end -function FLIGHTGROUP:SetAirboss(airboss) -self.airboss=airboss -return self -end -function FLIGHTGROUP:SetFuelLowThreshold(threshold) -self.fuellowthresh=threshold or 25 -return self -end -function FLIGHTGROUP:SetFuelLowRTB(switch) -if switch==false then -self.fuellowrtb=false -else -self.fuellowrtb=true -end -return self -end -function FLIGHTGROUP:SetOutOfAAMRTB(switch) -if switch==false then -self.outofAAMrtb=false -else -self.outofAAMrtb=true -end -return self -end -function FLIGHTGROUP:SetOutOfAGMRTB(switch) -if switch==false then -self.outofAGMrtb=false -else -self.outofAGMrtb=true -end -return self -end -function FLIGHTGROUP:SetFuelLowRefuel(switch) -if switch==false then -self.fuellowrefuel=false -else -self.fuellowrefuel=true -end -return self -end -function FLIGHTGROUP:SetFuelCriticalThreshold(threshold) -self.fuelcriticalthresh=threshold or 10 -return self -end -function FLIGHTGROUP:SetFuelCriticalRTB(switch) -if switch==false then -self.fuelcriticalrtb=false -else -self.fuelcriticalrtb=true -end -return self -end -function FLIGHTGROUP:SetDespawnAfterLanding() -self.despawnAfterLanding=true -return self -end -function FLIGHTGROUP:SetDespawnAfterHolding() -self.despawnAfterHolding=true -return self -end -function FLIGHTGROUP:IsParking(Element) -local is=self:Is("Parking") -if Element then -is=Element.status==OPSGROUP.ElementStatus.PARKING -end -return is -end -function FLIGHTGROUP:IsTaxiing(Element) -local is=self:Is("Taxiing") -if Element then -is=Element.status==OPSGROUP.ElementStatus.TAXIING -end -return is -end -function FLIGHTGROUP:IsAirborne(Element) -local is=self:Is("Airborne")or self:Is("Cruising") -if Element then -is=Element.status==OPSGROUP.ElementStatus.AIRBORNE -end -return is -end -function FLIGHTGROUP:IsCruising() -local is=self:Is("Cruising") -return is -end -function FLIGHTGROUP:IsLanding(Element) -local is=self:Is("Landing") -if Element then -is=Element.status==OPSGROUP.ElementStatus.LANDING -end -return is -end -function FLIGHTGROUP:IsLanded(Element) -local is=self:Is("Landed") -if Element then -is=Element.status==OPSGROUP.ElementStatus.LANDED -end -return is -end -function FLIGHTGROUP:IsArrived(Element) -local is=self:Is("Arrived") -if Element then -is=Element.status==OPSGROUP.ElementStatus.ARRIVED -end -return is -end -function FLIGHTGROUP:IsInbound() -local is=self:Is("Inbound") -return is -end -function FLIGHTGROUP:IsHolding() -local is=self:Is("Holding") -return is -end -function FLIGHTGROUP:IsGoing4Fuel() -local is=self:Is("Going4Fuel") -return is -end -function FLIGHTGROUP:IsLandingAt() -local is=self:Is("LandingAt") -return is -end -function FLIGHTGROUP:IsLandedAt() -local is=self:Is("LandedAt") -return is -end -function FLIGHTGROUP:IsFuelLow() -return self.fuellow -end -function FLIGHTGROUP:IsFuelCritical() -return self.fuelcritical -end -function FLIGHTGROUP:IsFuelGood() -local isgood=not(self.fuellow or self.fuelcritical) -return isgood -end -function FLIGHTGROUP:CanAirToGround(ExcludeGuns) -local ammo=self:GetAmmoTot() -if ExcludeGuns then -return ammo.MissilesAG+ammo.Rockets+ammo.Bombs>0 -else -return ammo.MissilesAG+ammo.Rockets+ammo.Bombs+ammo.Guns>0 -end -end -function FLIGHTGROUP:CanAirToAir(ExcludeGuns) -local ammo=self:GetAmmoTot() -if ExcludeGuns then -return ammo.MissilesAA>0 -else -return ammo.MissilesAA+ammo.Guns>0 -end -end -function FLIGHTGROUP:StartUncontrolled(delay) -if delay and delay>0 then -self:T2(self.lid..string.format("Starting uncontrolled group in %d seconds",delay)) -self:ScheduleOnce(delay,FLIGHTGROUP.StartUncontrolled,self) -else -local alive=self:IsAlive() -if alive~=nil then -local _delay=0 -if alive==false then -self:Activate() -_delay=1 -end -self:T(self.lid.."Starting uncontrolled group") -self.group:StartUncontrolled(_delay) -self.isUncontrolled=false -else -self:T(self.lid.."ERROR: Could not start uncontrolled group as it is NOT alive!") -end -end -return self -end -function FLIGHTGROUP:ClearToLand(Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,FLIGHTGROUP.ClearToLand,self) -else -if self:IsHolding()then -self:T(self.lid..string.format("Clear to land ==> setting holding flag to 1 (true)")) -self.flaghold:Set(1) -self.Tholding=nil -if self.stack then -self.stack.flightgroup=nil -self.stack=nil -end -end -end -return self -end -function FLIGHTGROUP:GetFuelMin() -local fuelmin=math.huge -for i,_element in pairs(self.elements)do -local element=_element -local unit=element.unit -local life=unit:GetLife() -if unit and unit:IsAlive()and life>1 then -local fuel=unit:GetFuel() -if fuelself.Twaiting+self.dTwait then -end -end -end -if mission and mission.updateDCSTask then -if(mission:GetType()==AUFTRAG.Type.ORBIT or mission:GetType()==AUFTRAG.Type.RECOVERYTANKER or mission:GetType()==AUFTRAG.Type.CAP)and mission.orbitVec2 then -local vec2=mission:GetTargetVec2() -local hdg=mission:GetTargetHeading() -local hdgchange=false -if mission.orbitLeg then -if UTILS.HdgDiff(hdg,mission.targetHeading)>0 then -hdgchange=true -end -end -local dist=UTILS.VecDist2D(vec2,mission.orbitVec2) -local distchange=dist>mission.orbitDeltaR -self:T3(self.lid..string.format("Checking orbit mission dist=%d meters",dist)) -if distchange or hdgchange then -self:T3(self.lid..string.format("Updating orbit!")) -local DCSTask=mission:GetDCSMissionTask() -local Task=mission:GetGroupWaypointTask(self) -self.controller:resetTask() -self:_SandwitchDCSTask(DCSTask,Task,false,1) -end -elseif mission.type==AUFTRAG.Type.CAPTUREZONE then -local Task=mission:GetGroupWaypointTask(self) -if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING or mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.STARTED then -self:_UpdateTask(Task,mission) -end -end -end -if self:IsParking()then -for _,_element in pairs(self.elements)do -local element=_element -if element.parking then -local dist=self:_GetDistToParking(element.parking,element.unit:GetCoord()) -self:T(self.lid..string.format("Distance to parking spot %d = %.1f meters",element.parking.TerminalID,dist)) -if dist>12 and element.engineOn then -self:ElementTaxiing(element) -end -else -end -end -end -else -self:_CheckDamage() -end -if self.verbose>=1 then -local nelem=self:CountElements() -local Nelem=#self.elements -local nTaskTot,nTaskSched,nTaskWP=self:CountRemainingTasks() -local nMissions=self:CountRemainingMissison() -local roe=self:GetROE()or-1 -local rot=self:GetROT()or-1 -local wpidxCurr=self.currentwp -local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr)or 0 -local wpidxNext=self:GetWaypointIndexNext()or 0 -local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext)or 0 -local wpN=#self.waypoints or 0 -local wpF=tostring(self.passedfinalwp) -local speed=UTILS.MpsToKnots(self.velocity or 0) -local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) -local alt=self.position and self.position.y or 0 -local hdg=self.heading or 0 -local formation=self.option.Formation or"unknown" -local life=self.life or 0 -local ammo=self:GetAmmoTot().Total -local ndetected=self.detectionOn and tostring(self.detectedunits:Count())or"Off" -local cargo=0 -for _,_element in pairs(self.elements)do -local element=_element -cargo=cargo+element.weightCargo -end -local home=self.homebase and self.homebase:GetName()or"unknown" -local dest=self.destbase and self.destbase:GetName()or"unknown" -local curr=self.currbase and self.currbase:GetName()or"N/A" -local text=string.format("%s [%d/%d]: ROE/ROT=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f | Base=%s [%s-->%s]", -fsmstate,nelem,Nelem,roe,rot,nTaskTot,nMissions,wpidxCurr,wpuidCurr,wpidxNext,wpuidNext,wpN,wpF,life,speed,speedEx,hdg,ammo,ndetected,cargo,curr,home,dest) -self:I(self.lid..text) -end -if self.verbose>=2 then -local text="Elements:" -for i,_element in pairs(self.elements)do -local element=_element -local name=element.name -local status=element.status -local unit=element.unit -local fuel=unit:GetFuel()or 0 -local life=unit:GetLifeRelative()or 0 -local lp=unit:GetLife() -local lp0=unit:GetLife0() -local parking=element.parking and tostring(element.parking.TerminalID)or"X" -local ammo=self:GetAmmoElement(element) -text=text..string.format("\n[%d] %s: status=%s, fuel=%.1f, life=%.1f [%.1f/%.1f], guns=%d, rockets=%d, bombs=%d, missiles=%d (AA=%d, AG=%d, AS=%s), parking=%s", -i,name,status,fuel*100,life*100,lp,lp0,ammo.Guns,ammo.Rockets,ammo.Bombs,ammo.Missiles,ammo.MissilesAA,ammo.MissilesAG,ammo.MissilesAS,parking) -end -if#self.elements==0 then -text=text.." none!" -end -self:I(self.lid..text) -end -if self.verbose>=4 and alive then -local ds=self.travelds -local dt=self.dTpositionUpdate -local v=ds/dt -local TmaxFuel=math.huge -for _,_element in pairs(self.elements)do -local element=_element -local fuel=element.unit:GetFuel()or 0 -local dFrel=element.fuelrel-fuel -local dFreldt=dFrel/dt -local Tfuel=fuel/dFreldt -if Tfuel Tfuel=%.1f min",element.name,fuel*100,dFrel*100,dFreldt*100*60,Tfuel/60)) -element.fuelrel=fuel -end -self:T(self.lid..string.format("Travelled ds=%.1f km dt=%.1f s ==> v=%.1f knots. Fuel left for %.1f min",self.traveldist/1000,dt,UTILS.MpsToKnots(v),TmaxFuel/60)) -end -if false then -for _,_element in pairs(self.elements)do -local element=_element -local unit=element.unit -if unit and unit:IsAlive()then -local vec3=unit:GetVec3() -if vec3 and element.pos then -local id=UTILS.GetMarkID() -trigger.action.lineToAll(-1,id,vec3,element.pos,{1,1,1,0.5},1) -end -element.pos=vec3 -end -end -end -if alive and self.group:IsAirborne(true)then -local fuelmin=self:GetFuelMin() -self:T2(self.lid..string.format("Fuel state=%d",fuelmin)) -if fuelmin>=self.fuellowthresh then -self.fuellow=false -end -if fuelmin>=self.fuelcriticalthresh then -self.fuelcritical=false -end -if fuelmin taxiing (if AI)",element.name)) -self:ElementEngineOn(element) -element.engineOn=true -end -end -end -end -function FLIGHTGROUP:OnEventTakeOff(EventData) -self:T3(self.lid.."EVENT: TakeOff") -if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then -local unit=EventData.IniUnit -local group=EventData.IniGroup -local unitname=EventData.IniUnitName -local element=self:GetElementByName(unitname) -if element then -self:T2(self.lid..string.format("EVENT: Element %s took off ==> airborne",element.name)) -self:ElementTakeoff(element,EventData.Place) -end -end -end -function FLIGHTGROUP:OnEventLanding(EventData) -if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then -local unit=EventData.IniUnit -local group=EventData.IniGroup -local unitname=EventData.IniUnitName -local element=self:GetElementByName(unitname) -local airbase=EventData.Place -local airbasename="unknown" -if airbase then -airbasename=tostring(airbase:GetName()) -end -if element then -self:T3(self.lid..string.format("EVENT: Element %s landed at %s ==> landed",element.name,airbasename)) -self:ElementLanded(element,airbase) -end -end -end -function FLIGHTGROUP:OnEventEngineShutdown(EventData) -if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then -local unit=EventData.IniUnit -local group=EventData.IniGroup -local unitname=EventData.IniUnitName -local element=self:GetElementByName(unitname) -if element then -element.engineOn=false -if element.unit and element.unit:IsAlive()then -local airbase=self:GetClosestAirbase() -local parking=self:GetParkingSpot(element,100,airbase) -if airbase and parking then -self:ElementArrived(element,airbase,parking) -self:T3(self.lid..string.format("EVENT: Element %s shut down engines ==> arrived",element.name)) -else -self:T3(self.lid..string.format("EVENT: Element %s shut down engines but is not parking. Is it dead?",element.name)) -end -else -end -end -end -end -function FLIGHTGROUP:OnEventCrash(EventData) -if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then -local unit=EventData.IniUnit -local group=EventData.IniGroup -local unitname=EventData.IniUnitName -local element=self:GetElementByName(unitname) -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -self:T(self.lid..string.format("EVENT: Element %s crashed ==> destroyed",element.name)) -self:ElementDestroyed(element) -end -end -end -function FLIGHTGROUP:OnEventUnitLost(EventData) -if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then -self:T2(self.lid..string.format("EVENT: Unit %s lost at t=%.3f",EventData.IniUnitName,timer.getTime())) -local unit=EventData.IniUnit -local group=EventData.IniGroup -local unitname=EventData.IniUnitName -local element=self:GetElementByName(unitname) -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -self:T(self.lid..string.format("EVENT: Element %s unit lost ==> destroyed t=%.3f",element.name,timer.getTime())) -self:ElementDestroyed(element) -end -end -end -function FLIGHTGROUP:onafterElementSpawned(From,Event,To,Element) -self:T(self.lid..string.format("Element spawned %s",Element.name)) -if Element.playerName then -self:_InitPlayerData(Element.playerName) -end -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.SPAWNED) -if Element.unit:InAir(not self.isHelo)then -self:__ElementAirborne(0.11,Element) -else -local spot=self:GetParkingSpot(Element,10) -if spot then -self:__ElementParking(0.11,Element,spot) -else -self:T(self.lid..string.format("Element spawned not in air but not on any parking spot.")) -self:__ElementParking(0.11,Element) -end -end -end -function FLIGHTGROUP:onafterElementParking(From,Event,To,Element,Spot) -if Spot then -self:_SetElementParkingAt(Element,Spot) -end -self:T(self.lid..string.format("Element parking %s at spot %s",Element.name,Element.parking and tostring(Element.parking.TerminalID)or"N/A")) -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.PARKING) -if self:IsTakeoffCold()then -elseif self:IsTakeoffHot()then -self:__ElementEngineOn(0.5,Element) -Element.engineOn=true -elseif self:IsTakeoffRunway()then -self:__ElementEngineOn(0.5,Element) -Element.engineOn=true -end -end -function FLIGHTGROUP:onafterElementEngineOn(From,Event,To,Element) -self:T(self.lid..string.format("Element %s started engines",Element.name)) -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.ENGINEON) -end -function FLIGHTGROUP:onafterElementTaxiing(From,Event,To,Element) -local TerminalID=Element.parking and tostring(Element.parking.TerminalID)or"N/A" -self:T(self.lid..string.format("Element taxiing %s. Parking spot %s is now free",Element.name,TerminalID)) -self:_SetElementParkingFree(Element) -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.TAXIING) -end -function FLIGHTGROUP:onafterElementTakeoff(From,Event,To,Element,airbase) -self:T(self.lid..string.format("Element takeoff %s at %s airbase.",Element.name,airbase and airbase:GetName()or"unknown")) -if Element.parking then -self:_SetElementParkingFree(Element) -end -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.TAKEOFF,airbase) -self:__ElementAirborne(0.01,Element) -end -function FLIGHTGROUP:onafterElementAirborne(From,Event,To,Element) -self:T2(self.lid..string.format("Element airborne %s",Element.name)) -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.AIRBORNE) -end -function FLIGHTGROUP:onafterElementLanded(From,Event,To,Element,airbase) -self:T2(self.lid..string.format("Element landed %s at %s airbase",Element.name,airbase and airbase:GetName()or"unknown")) -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.LANDED,airbase) -if self.isHelo then -local Spot=self:GetParkingSpot(Element,10,airbase) -if Spot then -self:_SetElementParkingAt(Element,Spot) -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.ARRIVED) -end -end -if self.despawnAfterLanding then -if self.legion then -if airbase and self.legion.airbase and airbase.AirbaseName==self.legion.airbase.AirbaseName then -if self:IsLanded()then -self:ReturnToLegion() -else -self:DespawnElement(Element) -end -end -else -self:DespawnElement(Element) -end -end -end -function FLIGHTGROUP:onafterElementArrived(From,Event,To,Element,airbase,Parking) -self:T(self.lid..string.format("Element arrived %s at %s airbase using parking spot %d",Element.name,airbase and airbase:GetName()or"unknown",Parking and Parking.TerminalID or-99)) -self:_SetElementParkingAt(Element,Parking) -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.ARRIVED) -end -function FLIGHTGROUP:onafterElementDestroyed(From,Event,To,Element) -self:GetParent(self).onafterElementDestroyed(self,From,Event,To,Element) -end -function FLIGHTGROUP:onafterElementDead(From,Event,To,Element) -if self.flightcontrol and Element.parking then -self.flightcontrol:SetParkingFree(Element.parking) -end -self:GetParent(self).onafterElementDead(self,From,Event,To,Element) -Element.parking=nil -end -function FLIGHTGROUP:onafterSpawned(From,Event,To) -self:T(self.lid..string.format("Flight spawned")) -if self.verbose>=1 then -local text=string.format("Initialized Flight Group %s:\n",self.groupname) -text=text..string.format("Unit type = %s\n",self.actype) -text=text..string.format("Speed max = %.1f Knots\n",UTILS.KmphToKnots(self.speedMax)) -text=text..string.format("Range max = %.1f km\n",self.rangemax/1000) -text=text..string.format("Ceiling = %.1f feet\n",UTILS.MetersToFeet(self.ceiling)) -text=text..string.format("Weight = %.1f kg\n",self:GetWeightTotal()) -text=text..string.format("Cargo bay = %.1f kg\n",self:GetFreeCargobay()) -text=text..string.format("Tanker type = %s\n",tostring(self.tankertype)) -text=text..string.format("Refuel type = %s\n",tostring(self.refueltype)) -text=text..string.format("AI = %s\n",tostring(self.isAI)) -text=text..string.format("Has EPLRS = %s\n",tostring(self.isEPLRS)) -text=text..string.format("Helicopter = %s\n",tostring(self.isHelo)) -text=text..string.format("Elements = %d\n",#self.elements) -text=text..string.format("Waypoints = %d\n",#self.waypoints) -text=text..string.format("Radio = %.1f MHz %s %s\n",self.radio.Freq,UTILS.GetModulationName(self.radio.Modu),tostring(self.radio.On)) -text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Rockets,self.ammo.Bombs,self.ammo.Missiles) -text=text..string.format("FSM state = %s\n",self:GetState()) -text=text..string.format("Is alive = %s\n",tostring(self.group:IsAlive())) -text=text..string.format("LateActivate = %s\n",tostring(self:IsLateActivated())) -text=text..string.format("Uncontrolled = %s\n",tostring(self:IsUncontrolled())) -text=text..string.format("Start Air = %s\n",tostring(self:IsTakeoffAir())) -text=text..string.format("Start Cold = %s\n",tostring(self:IsTakeoffCold())) -text=text..string.format("Start Hot = %s\n",tostring(self:IsTakeoffHot())) -text=text..string.format("Start Rwy = %s\n",tostring(self:IsTakeoffRunway())) -text=text..string.format("Elements:") -for i,_element in pairs(self.elements)do -local element=_element -text=text..string.format("\n[%d] %s: callsign=%s, modex=%s, player=%s",i,element.name,tostring(element.callsign),tostring(element.modex),tostring(element.playerName)) -end -self:I(self.lid..text) -end -self:_UpdatePosition() -self.isDead=false -self.isDestroyed=false -if self.isAI then -self:SwitchROE(self.option.ROE) -self:SwitchROT(self.option.ROT) -self:SwitchEPLRS(self.option.EPLRS) -self:SwitchInvisible(self.option.Invisible) -self:SwitchImmortal(self.option.Immortal) -self:SwitchFormation(self.option.Formation) -self:_SwitchTACAN() -if self.radioDefault then -self:SwitchRadio() -else -self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,self.radio.On) -end -if self.callsignDefault then -self:SwitchCallsign(self.callsignDefault.NumberSquad,self.callsignDefault.NumberGroup) -else -self:SetDefaultCallsign(self.callsign.NumberSquad,self.callsign.NumberGroup) -end -self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_JETT,self.jettisonWeapons) -self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_AB,self.prohibitAB) -self:GetGroup():SetOption(AI.Option.Air.id.RTB_ON_BINGO,false) -self:GetGroup():SetOption(AI.Option.Air.id.JETT_TANKS_IF_EMPTY,self.jettisonEmptyTanks) -self:__UpdateRoute(-0.5) -else -if self.currbase then -local flightcontrol=_DATABASE:GetFlightControl(self.currbase:GetName()) -if flightcontrol then -self:SetFlightControl(flightcontrol) -else -self:_UpdateMenu(0.5) -end -else -self:_UpdateMenu(0.5) -end -end -end -function FLIGHTGROUP:onafterParking(From,Event,To) -local airbase=self:GetClosestAirbase() -local airbasename=airbase:GetName()or"unknown" -self:T(self.lid..string.format("Flight is parking at airbase %s",airbasename)) -self.currbase=airbase -if not self.homebase then -self.homebase=airbase -end -self.Tparking=timer.getAbsTime() -local flightcontrol=_DATABASE:GetFlightControl(airbasename) -if flightcontrol then -self:SetFlightControl(flightcontrol) -if self.flightcontrol then -self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.PARKING) -end -else -self:T3(self.lid.."INFO: No flight control in onAfterParking!") -end -end -function FLIGHTGROUP:onafterTaxiing(From,Event,To) -self:T(self.lid..string.format("Flight is taxiing")) -self.Tparking=nil -if self.flightcontrol and self.flightcontrol:IsControlling(self)then -if self.isAI then -self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.TAKEOFF) -else -self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.TAXIOUT) -end -end -end -function FLIGHTGROUP:onafterTakeoff(From,Event,To,airbase) -self:T(self.lid..string.format("Flight takeoff from %s",airbase and airbase:GetName()or"unknown airbase")) -if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName()then -self.flightcontrol:_RemoveFlight(self) -self.flightcontrol=nil -end -end -function FLIGHTGROUP:onafterAirborne(From,Event,To) -self:T(self.lid..string.format("Flight airborne")) -self.currbase=nil -self:__Cruise(-0.01) -end -function FLIGHTGROUP:onafterCruise(From,Event,To) -self:T(self.lid..string.format("Flight cruising")) -self.Twaiting=nil -self.dTwait=nil -if self.isAI then -self:_CheckGroupDone(nil,120) -else -self:_UpdateMenu(0.1) -end -end -function FLIGHTGROUP:onafterLanding(From,Event,To) -self:T(self.lid..string.format("Flight is landing")) -self:_SetElementStatusAll(OPSGROUP.ElementStatus.LANDING) -if self.flightcontrol and self.flightcontrol:IsControlling(self)then -self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.LANDING) -end -self.Tholding=nil -if self.stack then -self.stack.flightgroup=nil -self.stack=nil -end -end -function FLIGHTGROUP:onafterLanded(From,Event,To,airbase) -self:T(self.lid..string.format("Flight landed at %s",airbase and airbase:GetName()or"unknown place")) -if self.flightcontrol and self.flightcontrol:IsControlling(self)then -self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.TAXIINB) -end -end -function FLIGHTGROUP:onafterLandedAt(From,Event,To) -self:T(self.lid..string.format("Flight landed at")) -if self:IsPickingup()then -self:__Loading(-1) -elseif self:IsTransporting()then -self:__Unloading(-1) -end -end -function FLIGHTGROUP:onafterArrived(From,Event,To) -self:T(self.lid..string.format("Flight arrived")) -if self.flightcontrol then -self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.ARRIVED) -end -if not self.isAI then -return -end -local airwing=self:GetAirwing() -if airwing and not(self:IsPickingup()or self:IsTransporting())then -self:T(self.lid..string.format("Airwing asset group %s arrived ==> Adding asset back to stock of airwing %s",self.groupname,airwing.alias)) -self:ReturnToLegion(1) -elseif self.isLandingAtAirbase then -local Template=UTILS.DeepCopy(self.template) -self.isLateActivated=false -Template.lateActivation=self.isLateActivated -self.isUncontrolled=true -Template.uncontrolled=self.isUncontrolled -local SpawnPoint=Template.route.points[1] -SpawnPoint.linkUnit=nil -SpawnPoint.helipadId=nil -SpawnPoint.airdromeId=nil -local airbase=self.isLandingAtAirbase -local AirbaseID=airbase:GetID() -if airbase:IsShip()then -SpawnPoint.linkUnit=AirbaseID -SpawnPoint.helipadId=AirbaseID -elseif airbase:IsHelipad()then -SpawnPoint.linkUnit=AirbaseID -SpawnPoint.helipadId=AirbaseID -elseif airbase:IsAirdrome()then -SpawnPoint.airdromeId=AirbaseID -end -SpawnPoint.alt=0 -SpawnPoint.type=COORDINATE.WaypointType.TakeOffParking -SpawnPoint.action=COORDINATE.WaypointAction.FromParkingArea -local units=Template.units -for i=#units,1,-1 do -local unit=units[i] -local element=self:GetElementByName(unit.name) -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -unit.parking=element.parking and element.parking.TerminalID or nil -unit.parking_id=nil -local vec3=element.unit:GetVec3() -local heading=element.unit:GetHeading() -unit.x=vec3.x -unit.y=vec3.z -unit.alt=vec3.y -unit.heading=math.rad(heading) -unit.psi=-unit.heading -else -table.remove(units,i) -end -end -self:_Respawn(0,Template) -self.isLandingAtAirbase=nil -if self:IsPickingup()then -self:__Loading(-1) -elseif self:IsTransporting()then -self:__Unloading(-1) -end -else -self:T(self.lid..string.format("Despawning group in 5 minutes after arrival!")) -self:Despawn(5*60) -end -end -function FLIGHTGROUP:onafterDead(From,Event,To) -if self.flightcontrol then -self.flightcontrol:_RemoveFlight(self) -self.flightcontrol=nil -end -self:GetParent(self).onafterDead(self,From,Event,To) -end -function FLIGHTGROUP:onbeforeUpdateRoute(From,Event,To,n,N) -local allowed=true -local trepeat=nil -if self:IsAlive()then -self:T3(self.lid.."Update route possible. Group is ALIVE") -elseif self:IsDead()then -self:T(self.lid.."Update route denied. Group is DEAD!") -allowed=false -elseif self:IsInUtero()then -self:T(self.lid.."Update route denied. Group is INUTERO!") -allowed=false -else -self:T(self.lid.."Update route denied ==> checking back in 5 sec") -trepeat=-5 -allowed=false -end -if allowed and self:IsUncontrolled()then -self:T(self.lid.."Update route denied. Group is UNCONTROLLED!") -local mission=self:GetMissionCurrent() -if mission and mission.type==AUFTRAG.Type.ALERT5 then -trepeat=nil -else -trepeat=-5 -end -allowed=false -end -if n and n<1 then -self:T(self.lid.."Update route denied because waypoint n<1!") -allowed=false -end -if not self.currentwp then -self:T(self.lid.."Update route denied because self.currentwp=nil!") -allowed=false -end -local Nn=n or self.currentwp+1 -if not Nn or Nn<1 then -self:T(self.lid.."Update route denied because N=nil or N<1") -trepeat=-5 -allowed=false -end -if self.taskcurrent>0 then -local task=self:GetTaskByID(self.taskcurrent) -if task then -if task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then -self:T2(self.lid.."Allowing update route for Task: PatrolZone") -elseif task.dcstask.id==AUFTRAG.SpecialTask.CAPTUREZONE then -self:T2(self.lid.."Allowing update route for Task: CaptureZone") -elseif task.dcstask.id==AUFTRAG.SpecialTask.RECON then -self:T2(self.lid.."Allowing update route for Task: ReconMission") -elseif task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then -self:T2(self.lid.."Allowing update route for Task: Patrol Race Track") -elseif task.dcstask.id==AUFTRAG.SpecialTask.HOVER then -self:T2(self.lid.."Allowing update route for Task: Hover") -elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then -self:T2(self.lid.."Allowing update route for Task: Relocate Cohort") -elseif task.description and task.description=="Task_Land_At"then -self:T2(self.lid.."Allowing update route for Task: Task_Land_At") -else -local taskname=task and task.description or"No description" -self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s",self.taskcurrent,tostring(taskname))) -allowed=false -end -else -self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d (>0!) but no task?!",self.taskcurrent)) -allowed=false -end -end -if not self.isAI then -allowed=false -end -self:T2(self.lid..string.format("Onbefore Updateroute in state %s: allowed=%s (repeat in %s)",self:GetState(),tostring(allowed),tostring(trepeat))) -if trepeat then -self:__UpdateRoute(trepeat,n) -end -return allowed -end -function FLIGHTGROUP:onafterUpdateRoute(From,Event,To,n,N) -n=n or self.currentwp+1 -N=N or#self.waypoints -N=math.min(N,#self.waypoints) -local wp={} -local speed=self.group and self.group:GetVelocityKMH()or 100 -local waypointType=COORDINATE.WaypointType.TurningPoint -local waypointAction=COORDINATE.WaypointAction.TurningPoint -if self:IsLanded()or self:IsLandedAt()or self:IsAirborne()==false then -waypointType=COORDINATE.WaypointType.TakeOff -end -local current=self:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO,waypointType,waypointAction,speed,true,nil,{},"Current") -table.insert(wp,current) -for i=n,N do -table.insert(wp,self.waypoints[i]) -end -if wp[2]then -self.speedWp=wp[2].speed -end -local hb=self.homebase and self.homebase:GetName()or"unknown" -local db=self.destbase and self.destbase:GetName()or"unknown" -self:T(self.lid..string.format("Updating route for WP #%d-%d [%s], homebase=%s destination=%s",n,#wp,self:GetState(),hb,db)) -if#wp>1 then -self:Route(wp) -else -if self:IsAirborne()then -self:T(self.lid.."No waypoints left ==> CheckGroupDone") -self:_CheckGroupDone() -end -end -end -function FLIGHTGROUP:onafterOutOfMissilesAA(From,Event,To) -self:T(self.lid.."Group is out of AA Missiles!") -if self.outofAAMrtb then -local airbase=self.destbase or self.homebase -self:__RTB(-5,airbase) -end -end -function FLIGHTGROUP:onafterOutOfMissilesAG(From,Event,To) -self:T(self.lid.."Group is out of AG Missiles!") -if self.outofAGMrtb then -local airbase=self.destbase or self.homebase -self:__RTB(-5,airbase) -end -end -function FLIGHTGROUP:_CheckGroupDone(delay,waittime) -local fsmstate=self:GetState() -if self:IsAlive()and self.isAI then -if delay and delay>0 then -self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done in %.3f seconds... (t=%.4f)",fsmstate,delay,timer.getTime())) -self:ScheduleOnce(delay,FLIGHTGROUP._CheckGroupDone,self) -else -self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done? (t=%.4f)",fsmstate,timer.getTime())) -if self:IsEngaging()then -self:T(self.lid.."Engaging! Group NOT done...") -return -end -local nTasks=self:CountRemainingTasks() -local nMissions=self:CountRemainingMissison() -local nTransports=self:CountRemainingTransports() -local nPaused=self:_CountPausedMissions() -if nPaused>0 and nPaused==nMissions then -local missionpaused=self:_GetPausedMission() -self:T(self.lid..string.format("Found paused mission %s [%s]. Unpausing mission...",missionpaused.name,missionpaused.type)) -self:UnpauseMission() -return -end -if self.isLandingAtAirbase then -self:T(self.lid..string.format("Landing at airbase %s! Group NOT done...",self.isLandingAtAirbase:GetName())) -return -end -if self:IsWaiting()then -self:T(self.lid.."Waiting! Group NOT done...") -return -end -self:T(self.lid..string.format("Remaining (final=%s): missions=%d, tasks=%d, transports=%d",tostring(self.passedfinalwp),nMissions,nTasks,nTransports)) -if self:HasPassedFinalWaypoint()or self:GetWaypointIndexNext()==1 then -if self.currentmission==nil and self.taskcurrent==0 and(self.cargoTransport==nil or self.cargoTransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.DELIVERED)then -if nTasks==0 and nMissions==0 and nTransports==0 then -local destbase=self.destbase or self.homebase -local destzone=self.destzone or self.homezone -if waittime then -self:T(self.lid..string.format("Passed Final WP and No current and/or future missions/tasks/transports. Waittime given ==> Waiting for %d sec!",waittime)) -self:Wait(waittime) -elseif destbase then -if self.currbase and self.currbase.AirbaseName==destbase.AirbaseName and self:IsParking()then -self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports AND parking at destination airbase ==> Arrived!") -self:Arrived() -else -self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") -self:__RTB(-0.1,destbase) -end -elseif destzone then -self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTZ!") -self:__RTZ(-0.1,destzone) -else -self:T(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!") -self:__Wait(-1) -end -else -if not self:IsParking()then -self:T(self.lid..string.format("Passed Final WP but Tasks=%d or Missions=%d left in the queue. Wait!",nTasks,nMissions)) -self:__Wait(-1) -end -end -else -self:T(self.lid..string.format("Passed Final WP but still have current Task (#%s) or Mission (#%s) left to do",tostring(self.taskcurrent),tostring(self.currentmission))) -end -else -self:T(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route in -0.01 sec",self:GetState())) -self:__UpdateRoute(-0.01) -end -end -end -end -function FLIGHTGROUP:onbeforeRTB(From,Event,To,airbase,SpeedTo,SpeedHold) -self:T(self.lid..string.format("RTB: before event=%s: %s --> %s to %s",Event,From,To,airbase and airbase:GetName()or"None")) -if self:IsAlive()then -local allowed=true -local Tsuspend=nil -if airbase==nil then -self:T(self.lid.."ERROR: Airbase is nil in RTB() call!") -allowed=false -end -if airbase and airbase:GetCoalition()~=self.group:GetCoalition()and airbase:GetCoalition()>0 then -self:T(self.lid..string.format("ERROR: Wrong airbase coalition %d in RTB() call! We allow only same as group %d or neutral airbases 0",airbase:GetCoalition(),self.group:GetCoalition())) -return false -end -if self.currbase and self.currbase:GetName()==airbase:GetName()then -self:T(self.lid.."WARNING: Currbase is already same as RTB airbase. RTB canceled!") -return false -end -if self:IsLanded()then -self:T(self.lid.."WARNING: Flight has already landed. RTB canceled!") -return false -end -if not self.group:IsAirborne(true)then -self:T(self.lid..string.format("WARNING: Group [%s] is not AIRBORNE ==> RTB event is suspended for 20 sec",self:GetState())) -allowed=false -Tsuspend=-20 -local groupspeed=self.group:GetVelocityMPS() -if groupspeed<=1 and not self:IsParking()then -self.RTBRecallCount=self.RTBRecallCount+1 -end -if self.RTBRecallCount>6 then -self:T(self.lid..string.format("WARNING: Group [%s] is not moving and was called RTB %d times. Assuming a problem and despawning!",self:GetState(),self.RTBRecallCount)) -self.RTBRecallCount=0 -self:Despawn(5) -return -end -end -if self:IsFuelGood()then -local Ntot,Nsched,Nwp=self:CountRemainingTasks() -if self.taskcurrent>0 then -self:T(self.lid..string.format("WARNING: Got current task ==> RTB event is suspended for 10 sec")) -Tsuspend=-10 -allowed=false -end -if Nsched>0 then -self:T(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> RTB event is suspended for 10 sec",Nsched)) -Tsuspend=-10 -allowed=false -end -if Nwp>0 then -self:T(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> RTB event is suspended for 10 sec",Nwp)) -Tsuspend=-10 -allowed=false -end -if self.Twaiting and self.dTwait then -self:T(self.lid..string.format("WARNING: Group is Waiting for a specific duration ==> RTB event is canceled",Nwp)) -allowed=false -end -end -if Tsuspend and not allowed then -self:__RTB(Tsuspend,airbase,SpeedTo,SpeedHold) -end -return allowed -else -self:T(self.lid.."WARNING: Group is not alive! RTB call not allowed.") -return false -end -end -function FLIGHTGROUP:onafterRTB(From,Event,To,airbase,SpeedTo,SpeedHold,SpeedLand) -self:T(self.lid..string.format("RTB: event=%s: %s --> %s to %s",Event,From,To,airbase:GetName())) -self.destbase=airbase -self:CancelAllMissions() -self:_LandAtAirbase(airbase,SpeedTo,SpeedHold,SpeedLand) -end -function FLIGHTGROUP:onbeforeLandAtAirbase(From,Event,To,airbase) -if self:IsAlive()then -local allowed=true -local Tsuspend=nil -if airbase==nil then -self:T(self.lid.."ERROR: Airbase is nil in LandAtAirase() call!") -allowed=false -end -if airbase and airbase:GetCoalition()~=self.group:GetCoalition()and airbase:GetCoalition()>0 then -self:T(self.lid..string.format("ERROR: Wrong airbase coalition %d in LandAtAirbase() call! We allow only same as group %d or neutral airbases 0",airbase:GetCoalition(),self.group:GetCoalition())) -return false -end -if self.currbase and self.currbase:GetName()==airbase:GetName()then -self:T(self.lid.."WARNING: Currbase is already same as LandAtAirbase airbase. LandAtAirbase canceled!") -return false -end -if self:IsLanded()then -self:T(self.lid.."WARNING: Flight has already landed. LandAtAirbase canceled!") -return false -end -if self:IsParking()then -allowed=false -Tsuspend=-30 -self:T(self.lid.."WARNING: Flight is parking. LandAtAirbase call delayed by 30 sec") -elseif self:IsTaxiing()then -allowed=false -Tsuspend=-1 -self:T(self.lid.."WARNING: Flight is parking. LandAtAirbase call delayed by 1 sec") -end -if Tsuspend and not allowed then -self:__LandAtAirbase(Tsuspend,airbase) -end -return allowed -else -self:T(self.lid.."WARNING: Group is not alive! LandAtAirbase call not allowed") -return false -end -end -function FLIGHTGROUP:onafterLandAtAirbase(From,Event,To,airbase) -self.isLandingAtAirbase=airbase -self:_LandAtAirbase(airbase) -end -function FLIGHTGROUP:_LandAtAirbase(airbase,SpeedTo,SpeedHold,SpeedLand) -self.currbase=airbase -self:_PassedFinalWaypoint(true,"_LandAtAirbase") -self.Twaiting=nil -self.dTwait=nil -SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise) -SpeedHold=SpeedHold or(self.isHelo and 80 or 250) -SpeedLand=SpeedLand or(self.isHelo and 40 or 170) -self.Tholding=nil -local text=string.format("Flight group set to hold at airbase %s. SpeedTo=%d, SpeedHold=%d, SpeedLand=%d",airbase:GetName(),SpeedTo,SpeedHold,SpeedLand) -self:T(self.lid..text) -local althold=self.isHelo and 1000+math.random(10)*100 or math.random(4,10)*1000 -local c0=self:GetCoordinate() -local p0=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(althold)) -local p1=nil -local wpap=nil -local fc=_DATABASE:GetFlightControl(airbase:GetName()) -if fc and self.isAI then -local stack=fc:_GetHoldingStack(self) -if stack then -stack.flightgroup=self -self.stack=stack -p0=stack.pos0 -p1=stack.pos1 -if false then -p0:MarkToAll(string.format("%s: Holding stack P0, alt=%d meters",self:GetName(),p0.y)) -p1:MarkToAll(string.format("%s: Holding stack P1, alt=%d meters",self:GetName(),p0.y)) -end -else -end -self:SetFlightControl(fc) -self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.INBOUND) -local callsign=self:GetCallsignName() -local text=string.format("%s, %s, inbound for landing",fc.alias,callsign) -fc:TransmissionPilot(text,self) -local text=string.format("%s, %s, roger, hold at angels %d. Report entering the pattern.",callsign,fc.alias,stack.angels) -fc:TransmissionTower(text,self,10) -end -local c1=c0:GetIntermediateCoordinate(p0,0.25):SetAltitude(self.altitudeCruise,true) -local c2=c0:GetIntermediateCoordinate(p0,0.75):SetAltitude(self.altitudeCruise,true) -local x1=self.isHelo and UTILS.NMToMeters(2.0)or UTILS.NMToMeters(10) -local x2=self.isHelo and UTILS.NMToMeters(1.0)or UTILS.NMToMeters(5) -local alpha=math.rad(3) -local h1=x1*math.tan(alpha) -local h2=x2*math.tan(alpha) -local runway=airbase:GetActiveRunwayLanding() -self.flaghold:Set(0) -local holdtime=2*60 -if fc or self.airboss then -holdtime=nil -end -local TaskArrived=self.group:TaskFunction("FLIGHTGROUP._ReachedHolding",self) -local TaskOrbit=self.group:TaskOrbit(p0,nil,UTILS.KnotsToMps(SpeedHold),p1) -local TaskLand=self.group:TaskCondition(nil,self.flaghold.UserFlagName,1,nil,holdtime) -local TaskHold=self.group:TaskControlled(TaskOrbit,TaskLand) -local TaskKlar=self.group:TaskFunction("FLIGHTGROUP._ClearedToLand",self) -local wp={} -wp[#wp+1]=c1:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(SpeedTo),true,nil,{},"Climb") -wp[#wp+1]=c2:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(SpeedTo),true,nil,{},"Descent") -wp[#wp+1]=p0:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(SpeedTo),true,nil,{TaskArrived,TaskHold,TaskKlar},"Holding Point") -if airbase:IsAirdrome()then -local TaskFinal=self.group:TaskFunction("FLIGHTGROUP._OnFinal",self) -local rheading -if runway then -rheading=runway.heading-180 -else -local wind=airbase:GetCoordinate():GetWind() -rheading=-wind -end -local papp=airbase:GetCoordinate():Translate(x1,rheading):SetAltitude(h1) -wp[#wp+1]=papp:WaypointAirTurningPoint("BARO",UTILS.KnotsToKmph(SpeedLand),{TaskFinal},"Final Approach") -local pland=airbase:GetCoordinate():Translate(x2,rheading):SetAltitude(h2) -wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand),airbase,{},"Landing") -elseif airbase:IsShip()or airbase:IsHelipad()then -local pland=airbase:GetCoordinate() -wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand),airbase,{},"Landing") -end -if self.isAI then -self:Route(wp,1.0) -end -end -function FLIGHTGROUP:onbeforeWait(From,Event,To,Duration,Altitude,Speed) -local allowed=true -local Tsuspend=nil -if self.taskcurrent>0 and not self:IsLandedAt()then -self:T(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!")) -Tsuspend=-30 -allowed=false -end -if self.cargoTransport and not self:IsLandedAt()then -end -if Tsuspend and not allowed then -self:__Wait(Tsuspend,Duration,Altitude,Speed) -end -return allowed -end -function FLIGHTGROUP:onafterWait(From,Event,To,Duration,Altitude,Speed) -local Coord=self:GetCoordinate() -if Altitude then -Altitude=UTILS.FeetToMeters(Altitude) -else -Altitude=self.altitudeCruise -end -Speed=Speed or(self.isHelo and 20 or 250) -local text=string.format("Group set to wait/orbit at altitude %d m and speed %.1f km/h for %s seconds",Altitude,Speed,tostring(Duration)) -self:T(self.lid..text) -self.flaghold:Set(0) -local TaskOrbit=self.group:TaskOrbit(Coord,Altitude,UTILS.KnotsToMps(Speed)) -local TaskStop=self.group:TaskCondition(nil,self.flaghold.UserFlagName,1,nil,Duration) -local TaskCntr=self.group:TaskControlled(TaskOrbit,TaskStop) -local TaskOver=self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting",self) -local DCSTasks -if Duration or true then -DCSTasks=self.group:TaskCombo({TaskCntr,TaskOver}) -else -DCSTasks=self.group:TaskCombo({TaskOrbit,TaskOver}) -end -self:PushTask(DCSTasks) -self.Twaiting=timer.getAbsTime() -self.dTwait=Duration -end -function FLIGHTGROUP:onafterRefuel(From,Event,To,Coordinate) -local text=string.format("Flight group set to refuel at the nearest tanker") -self:T(self.lid..text) -self:PauseMission() -local TaskRefuel=self.group:TaskRefueling() -local TaskFunction=self.group:TaskFunction("FLIGHTGROUP._FinishedRefuelling",self) -local DCSTasks={TaskRefuel,TaskFunction} -local Speed=self.speedCruise -local coordinate=self:GetCoordinate() -Coordinate=Coordinate or coordinate:Translate(UTILS.NMToMeters(5),self.group:GetHeading(),true) -local wp0=coordinate:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true) -local wp9=Coordinate:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,"Refuel") -self:Route({wp0,wp9},1) -end -function FLIGHTGROUP:onafterRefueled(From,Event,To) -local text=string.format("Flight group finished refuelling") -self:T(self.lid..text) -self:_CheckGroupDone(1) -end -function FLIGHTGROUP:onafterHolding(From,Event,To) -self.flaghold:Set(0) -if self.despawnAfterHolding then -if self.legion then -self:ReturnToLegion(1) -else -self:Despawn(1) -end -return -end -self.Tholding=timer.getAbsTime() -local text=string.format("Flight group %s is HOLDING now",self.groupname) -self:T(self.lid..text) -if self.flightcontrol then -self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.HOLDING) -if self.isAI then -local callsign=self:GetCallsignName() -local text=string.format("%s, %s, arrived at holding pattern",self.flightcontrol.alias,callsign) -if self.stack then -text=text..string.format(", angels %d.",self.stack.angels) -end -self.flightcontrol:TransmissionPilot(text,self) -local text=string.format("%s, roger, fly heading %d and wait for landing clearance",callsign,self.stack.heading) -self.flightcontrol:TransmissionTower(text,self,10) -end -elseif self.airboss then -if self.isHelo then -local carrierpos=self.airboss:GetCoordinate() -local carrierheading=self.airboss:GetHeading() -local Distance=UTILS.NMToMeters(5) -local Angle=carrierheading+90 -local altitude=math.random(12,25)*100 -local oc=carrierpos:Translate(Distance,Angle):SetAltitude(altitude,true) -local TaskOrbit=self.group:TaskOrbit(oc,nil,UTILS.KnotsToMps(50)) -local TaskLand=self.group:TaskCondition(nil,self.flaghold.UserFlagName,1) -local TaskHold=self.group:TaskControlled(TaskOrbit,TaskLand) -local TaskKlar=self.group:TaskFunction("FLIGHTGROUP._ClearedToLand",self) -local DCSTask=self.group:TaskCombo({TaskOrbit,TaskHold,TaskKlar}) -self:SetTask(DCSTask) -end -end -end -function FLIGHTGROUP:onafterEngageTarget(From,Event,To,Target) -local DCStask=nil -if Target:IsInstanceOf("UNIT")or Target:IsInstanceOf("STATIC")then -DCStask=self:GetGroup():TaskAttackUnit(Target,true) -elseif Target:IsInstanceOf("GROUP")then -DCStask=self:GetGroup():TaskAttackGroup(Target,nil,nil,nil,nil,nil,nil,true) -elseif Target:IsInstanceOf("SET_UNIT")then -local DCSTasks={} -for _,_unit in pairs(Target:GetSet())do -local unit=_unit -local task=self:GetGroup():TaskAttackUnit(unit,true) -table.insert(DCSTasks) -end -DCStask=self:GetGroup():TaskCombo(DCSTasks) -elseif Target:IsInstanceOf("SET_GROUP")then -local DCSTasks={} -for _,_unit in pairs(Target:GetSet())do -local unit=_unit -local task=self:GetGroup():TaskAttackGroup(Target,nil,nil,nil,nil,nil,nil,true) -table.insert(DCSTasks) -end -DCStask=self:GetGroup():TaskCombo(DCSTasks) -else -self:T("ERROR: unknown Target in EngageTarget! Needs to be a UNIT, STATIC, GROUP, SET_UNIT or SET_GROUP") -return -end -local Task=self:NewTaskScheduled(DCStask,1,"Engage_Target",0) -Task.backupROE=self:GetROE() -self:SwitchROE(ENUMS.ROE.OpenFire) -local mission=self:GetMissionCurrent() -if mission then -self:PauseMission() -end -self:TaskExecute(Task) -end -function FLIGHTGROUP:onafterDisengage(From,Event,To) -self:T(self.lid.."Disengage target") -end -function FLIGHTGROUP:onbeforeLandAt(From,Event,To,Coordinate,Duration) -return self.isHelo -end -function FLIGHTGROUP:onafterLandAt(From,Event,To,Coordinate,Duration) -self:T(self.lid..string.format("Landing at Coordinate for %s seconds",tostring(Duration))) -Coordinate=Coordinate or self:GetCoordinate() -local DCStask=self.group:TaskLandAtVec2(Coordinate:GetVec2(),Duration) -local Task=self:NewTaskScheduled(DCStask,1,"Task_Land_At",0) -self:TaskExecute(Task) -end -function FLIGHTGROUP:onafterFuelLow(From,Event,To) -local fuel=self:GetFuelMin()or 0 -local text=string.format("Low fuel %d for flight group %s",fuel,self.groupname) -self:T(self.lid..text) -self.fuellow=true -local airbase=self.destbase or self.homebase -if self.fuellowrefuel and self.refueltype then -local tanker=self:FindNearestTanker(50) -if tanker then -self:T(self.lid..string.format("Send to refuel at tanker %s",tanker:GetName())) -local coordinate=self:GetCoordinate():GetIntermediateCoordinate(tanker:GetCoordinate(),0.75) -self:Refuel(coordinate) -return -end -end -if airbase and self.fuellowrtb then -self:RTB(airbase) -end -end -function FLIGHTGROUP:onafterFuelCritical(From,Event,To) -local text=string.format("Critical fuel for flight group %s",self.groupname) -self:T(self.lid..text) -self.fuelcritical=true -local airbase=self.destbase or self.homebase -if airbase and self.fuelcriticalrtb and not self:IsGoing4Fuel()then -self:RTB(airbase) -end -end -function FLIGHTGROUP._ReachedHolding(group,flightgroup) -flightgroup:T2(flightgroup.lid..string.format("Group reached holding point")) -flightgroup:__Holding(-1) -end -function FLIGHTGROUP._ClearedToLand(group,flightgroup) -flightgroup:T2(flightgroup.lid..string.format("Group was cleared to land")) -flightgroup:__Landing(-1) -end -function FLIGHTGROUP._OnFinal(group,flightgroup) -flightgroup:T2(flightgroup.lid..string.format("Group on final approach")) -local fc=flightgroup.flightcontrol -if fc and fc:IsControlling(flightgroup)then -fc:_FlightOnFinal(flightgroup) -end -end -function FLIGHTGROUP._FinishedRefuelling(group,flightgroup) -flightgroup:T2(flightgroup.lid..string.format("Group finished refueling")) -flightgroup:__Refueled(-1) -end -function FLIGHTGROUP._FinishedWaiting(group,flightgroup) -flightgroup:T(flightgroup.lid..string.format("Group finished waiting")) -flightgroup.Twaiting=nil -flightgroup.dTwait=nil -flightgroup:_CheckGroupDone(0.1) -end -function FLIGHTGROUP:_InitGroup(Template) -if self.groupinitialized then -self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") -return -end -local group=self.group -local template=Template or self:_GetTemplate() -self.isHelo=group:IsHelicopter() -self.isUncontrolled=template.uncontrolled -self.isLateActivated=template.lateActivation -self.speedMax=group:GetSpeedMax() -if self.speedMax>3.6 then -self.isMobile=true -else -self.isMobile=false -end -local speedCruiseLimit=self.isHelo and UTILS.KnotsToKmph(110)or UTILS.KnotsToKmph(380) -self.speedCruise=math.min(self.speedMax*0.7,speedCruiseLimit) -self.ammo=self:GetAmmoTot() -self.radio.Freq=tonumber(template.frequency) -self.radio.Modu=tonumber(template.modulation) -self.radio.On=template.communication -local callsign=template.units[1].callsign -if type(callsign)=="number"then -local cs=tostring(callsign) -callsign={} -callsign[1]=cs:sub(1,1) -callsign[2]=cs:sub(2,2) -callsign[3]=cs:sub(3,3) -end -self.callsign.NumberSquad=tonumber(callsign[1]) -self.callsign.NumberGroup=tonumber(callsign[2]) -self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) -if self.isHelo then -self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300 -else -self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group -end -self:SetDefaultTACAN(nil,nil,nil,nil,true) -self.tacan=UTILS.DeepCopy(self.tacanDefault) -self.isAI=not self:_IsHuman(group) -if not self.isAI then -self.menu=self.menu or{} -self.menu.atc=self.menu.atc or{} -self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group,"ATC") -self.menu.atc.help=self.menu.atc.help or MENU_GROUP:New(self.group,"Help",self.menu.atc.root) -end -local units=self.group:GetUnits() -local dcsgroup=Group.getByName(self.groupname) -local size0=dcsgroup:getInitialSize() -if#units~=size0 then -self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!",#units,size0)) -end -for _,unit in pairs(units)do -self:_AddElementByName(unit:GetName()) -end -self.groupinitialized=true -return self -end -function FLIGHTGROUP:GetHomebaseFromWaypoints() -local wp=self.waypoints0 and self.waypoints0[1]or nil -if wp then -if wp and wp.action and wp.action==COORDINATE.WaypointAction.FromParkingArea -or wp.action==COORDINATE.WaypointAction.FromParkingAreaHot -or wp.action==COORDINATE.WaypointAction.FromRunway then -local airbaseID=nil -if wp.airdromeId then -airbaseID=wp.airdromeId -else -airbaseID=-wp.helipadId -end -local airbase=AIRBASE:FindByID(airbaseID) -return airbase -end -end -return nil -end -function FLIGHTGROUP:FindNearestAirbase(Radius) -local coord=self:GetCoordinate() -local dmin=math.huge -local airbase=nil -for _,_airbase in pairs(AIRBASE.GetAllAirbases())do -local ab=_airbase -local coalitionAB=ab:GetCoalition() -if coalitionAB==self:GetCoalition()or coalitionAB==coalition.side.NEUTRAL then -if airbase then -local d=ab:GetCoordinate():Get2DDistance(coord) -if d1 then -table.remove(self.waypoints,#self.waypoints) -else -self.destbase=self.homebase -end -self:T(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination",#self.waypoints,self.homebase and self.homebase:GetName()or"unknown",self.destbase and self.destbase:GetName()or"uknown")) -if#self.waypoints>0 then -if#self.waypoints==1 then -self:_PassedFinalWaypoint(true,"FLIGHTGROUP:InitWaypoints #self.waypoints==1") -end -end -return self -end -function FLIGHTGROUP:AddWaypoint(Coordinate,Speed,AfterWaypointWithID,Altitude,Updateroute) -local coordinate=self:_CoordinateFromObject(Coordinate) -local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) -Speed=Speed or self:GetSpeedCruise() -self:T3(self.lid..string.format("Waypoint Speed=%.1f knots",Speed)) -local alttype=COORDINATE.WaypointAltType.BARO -if self.isHelo then -alttype=COORDINATE.WaypointAltType.RADIO -end -local wp=coordinate:WaypointAir(alttype,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(Speed),true,nil,{}) -local waypoint=self:_CreateWaypoint(wp) -if Altitude then -waypoint.alt=UTILS.FeetToMeters(Altitude) -end -self:_AddWaypoint(waypoint,wpnumber) -self:T(self.lid..string.format("Adding AIR waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d",wpnumber,Speed,self.currentwp,#self.waypoints)) -if Updateroute==nil or Updateroute==true then -self:__UpdateRoute(-0.01) -end -return waypoint -end -function FLIGHTGROUP:AddWaypointLanding(Airbase,Speed,AfterWaypointWithID,Altitude,Updateroute) -local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) -if wpnumber>self.currentwp then -self:_PassedFinalWaypoint(false,"AddWaypointLanding") -end -Speed=Speed or self.speedCruise -local Coordinate=Airbase:GetCoordinate() -local wp=Coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO,COORDINATE.WaypointType.Land,COORDINATE.WaypointAction.Landing,Speed,nil,Airbase,{},"Landing Temp",nil) -local waypoint=self:_CreateWaypoint(wp) -if Altitude then -waypoint.alt=UTILS.FeetToMeters(Altitude) -end -self:_AddWaypoint(waypoint,wpnumber) -self:T(self.lid..string.format("Adding AIR waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d",wpnumber,Speed,self.currentwp,#self.waypoints)) -if Updateroute==nil or Updateroute==true then -self:__UpdateRoute(-1) -end -return waypoint -end -function FLIGHTGROUP:GetPlayerElement() -for _,_element in pairs(self.elements)do -local element=_element -if not element.ai then -return element -end -end -return nil -end -function FLIGHTGROUP:GetPlayerName() -local playerElement=self:GetPlayerElement() -if playerElement then -return playerElement.playerName -end -return nil -end -function FLIGHTGROUP:_SetElementParkingAt(Element,Spot) -Element.parking=Spot -if Spot then -self:T(self.lid..string.format("Element %s is parking on spot %d",Element.name,Spot.TerminalID)) -local fc=_DATABASE:GetFlightControl(Spot.AirbaseName) -if fc and not self.flightcontrol then -self:SetFlightControl(fc) -end -if self.flightcontrol then -self.flightcontrol:SetParkingOccupied(Element.parking,Element.name) -end -end -end -function FLIGHTGROUP:_SetElementParkingFree(Element) -if Element.parking then -if self.flightcontrol then -self.flightcontrol:SetParkingFree(Element.parking) -end -Element.parking=nil -end -end -function FLIGHTGROUP:_GetOnboardNumber(unitname) -local group=UNIT:FindByName(unitname):GetGroup() -local units=group:GetTemplate().units -local numbers={} -for _,unit in pairs(units)do -if unitname==unit.name then -return tostring(unit.onboard_num) -end -end -return nil -end -function FLIGHTGROUP:_IsHumanUnit(unit) -local playerunit=self:_GetPlayerUnitAndName(unit:GetName()) -if playerunit then -return true -else -return false -end -end -function FLIGHTGROUP:_IsHuman(group) -local units=group:GetUnits() -for _,_unit in pairs(units)do -local human=self:_IsHumanUnit(_unit) -if human then -return true -end -end -return false -end -function FLIGHTGROUP:_GetPlayerUnitAndName(_unitName) -self:F2(_unitName) -if _unitName~=nil then -local DCSunit=Unit.getByName(_unitName) -if DCSunit then -local playername=DCSunit:getPlayerName() -local unit=UNIT:Find(DCSunit) -if DCSunit and unit and playername then -return unit,playername -end -end -end -return nil,nil -end -function FLIGHTGROUP:GetParkingSpot(element,maxdist,airbase) -local coord=element.unit:GetCoordinate() -airbase=airbase or self:GetClosestAirbase() -local parking=airbase.parking -if airbase and airbase:IsShip()then -if#parking>1 then -coord=airbase:GetRelativeCoordinate(coord.x,coord.y,coord.z) -else -coord.x=0 -coord.z=0 -maxdist=500 -end -end -local spot=nil -local dist=nil -local distmin=math.huge -for _,_parking in pairs(parking)do -local parking=_parking -dist=coord:Get2DDistance(parking.Coordinate) -if distsafedist) -return safe -end -local function _clients() -local clients=_DATABASE.CLIENTS -local coords={} -for clientname,client in pairs(clients)do -local template=_DATABASE:GetGroupTemplateFromUnitName(clientname) -local units=template.units -for i,unit in pairs(units)do -local coord=COORDINATE:New(unit.x,unit.alt,unit.y) -coords[unit.name]=coord -end -end -return coords -end -local airbasecategory=airbase:GetAirbaseCategory() -local parkingdata=airbase:GetParkingSpotsTable() -local obstacles={} -for _,_parkingspot in pairs(parkingdata)do -local parkingspot=_parkingspot -local _,_,_,_units,_statics,_sceneries=parkingspot.Coordinate:ScanObjects(scanradius,scanunits,scanstatics,scanscenery) -for _,_unit in pairs(_units)do -local unit=_unit -local _coord=unit:GetCoordinate() -local _size=self:_GetObjectSize(unit:GetDCSObject()) -local _name=unit:GetName() -table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="unit"}) -end -local clientcoords=_clients() -for clientname,_coord in pairs(clientcoords)do -table.insert(obstacles,{coord=_coord,size=15,name=clientname,type="client"}) -end -for _,static in pairs(_statics)do -local _vec3=static:getPoint() -local _coord=COORDINATE:NewFromVec3(_vec3) -local _name=static:getName() -local _size=self:_GetObjectSize(static) -table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="static"}) -end -for _,scenery in pairs(_sceneries)do -local _vec3=scenery:getPoint() -local _coord=COORDINATE:NewFromVec3(_vec3) -local _name=scenery:getTypeName() -local _size=self:_GetObjectSize(scenery) -table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="scenery"}) -end -end -local parking={} -local terminaltype=self:_GetTerminal(self.attribute,airbase:GetAirbaseCategory()) -for i,_element in pairs(self.elements)do -local element=_element -local gotit=false -for _,_parkingspot in pairs(parkingdata)do -local parkingspot=_parkingspot -if AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype)then -local free=true -local problem=nil -if verysafe and parkingspot.TOAC then -free=false -self:T2(self.lid..string.format("Parking spot %d is occupied by other aircraft taking off (TOAC).",parkingspot.TerminalID)) -end -for _,obstacle in pairs(obstacles)do -local dist=parkingspot.Coordinate:Get2DDistance(obstacle.coord) -local safe=_overlap(element.size,obstacle.size,dist) -if not safe then -free=false -problem=obstacle -problem.dist=dist -break -end -end -if self.flightcontrol and self.flightcontrol.airbasename==airbase:GetName()then -local problem=self.flightcontrol:IsParkingReserved(parkingspot)or self.flightcontrol:IsParkingOccupied(parkingspot) -if problem then -free=false -end -end -if free then -table.insert(parking,parkingspot) -self:T2(self.lid..string.format("Parking spot %d is free for element %s!",parkingspot.TerminalID,element.name)) -table.insert(obstacles,{coord=parkingspot.Coordinate,size=element.size,name=element.name,type="element"}) -gotit=true -break -else -self:T2(self.lid..string.format("Parking spot %d is occupied or not big enough!",parkingspot.TerminalID)) -end -end -end -if not gotit then -self:T(self.lid..string.format("WARNING: No free parking spot for element %s",element.name)) -return nil -end -end -return parking -end -function FLIGHTGROUP:_GetObjectSize(DCSobject) -local DCSdesc=DCSobject:getDesc() -if DCSdesc.box then -local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) -local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) -local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) -return math.max(x,z),x,y,z -end -return 0,0,0,0 -end -function FLIGHTGROUP:_GetAttribute() -local attribute=FLIGHTGROUP.Attribute.OTHER -local group=self.group -if group then -local transportplane=group:HasAttribute("Transports")and group:HasAttribute("Planes") -local awacs=group:HasAttribute("AWACS") -local fighter=group:HasAttribute("Fighters")or group:HasAttribute("Interceptors")or group:HasAttribute("Multirole fighters")or(group:HasAttribute("Bombers")and not group:HasAttribute("Strategic bombers")) -local bomber=group:HasAttribute("Strategic bombers") -local tanker=group:HasAttribute("Tankers") -local uav=group:HasAttribute("UAVs") -local transporthelo=group:HasAttribute("Transport helicopters") -local attackhelicopter=group:HasAttribute("Attack helicopters") -if transportplane then -attribute=FLIGHTGROUP.Attribute.AIR_TRANSPORTPLANE -elseif awacs then -attribute=FLIGHTGROUP.Attribute.AIR_AWACS -elseif fighter then -attribute=FLIGHTGROUP.Attribute.AIR_FIGHTER -elseif bomber then -attribute=FLIGHTGROUP.Attribute.AIR_BOMBER -elseif tanker then -attribute=FLIGHTGROUP.Attribute.AIR_TANKER -elseif transporthelo then -attribute=FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO -elseif attackhelicopter then -attribute=FLIGHTGROUP.Attribute.AIR_ATTACKHELO -elseif uav then -attribute=FLIGHTGROUP.Attribute.AIR_UAV -end -end -return attribute -end -function FLIGHTGROUP:_GetTerminal(_attribute,_category) -local _terminal=AIRBASE.TerminalType.OpenBig -if _attribute==FLIGHTGROUP.Attribute.AIR_FIGHTER or _attribute==FLIGHTGROUP.Attribute.AIR_UAV then -_terminal=AIRBASE.TerminalType.FighterAircraft -elseif _attribute==FLIGHTGROUP.Attribute.AIR_BOMBER or _attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTPLANE or _attribute==FLIGHTGROUP.Attribute.AIR_TANKER or _attribute==FLIGHTGROUP.Attribute.AIR_AWACS then -_terminal=AIRBASE.TerminalType.OpenBig -elseif _attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO or _attribute==FLIGHTGROUP.Attribute.AIR_ATTACKHELO then -_terminal=AIRBASE.TerminalType.HelicopterUsable -else -end -if _category==Airbase.Category.SHIP then -if not(_attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO or _attribute==FLIGHTGROUP.Attribute.AIR_ATTACKHELO)then -_terminal=AIRBASE.TerminalType.OpenMedOrBig -end -end -return _terminal -end -function FLIGHTGROUP:_UpdateMenu(delay) -if delay and delay>0 then -self:ScheduleOnce(delay,FLIGHTGROUP._UpdateMenu,self) -else -local player=self:GetPlayerElement() -if player and player.status~=OPSGROUP.ElementStatus.DEAD then -if self.verbose>=2 then -local text=string.format("Updating MENU: State=%s, ATC=%s [%s]",self:GetState(), -self.flightcontrol and self.flightcontrol.airbasename or"None",self.flightcontrol and self.flightcontrol:GetFlightStatus(self)or"Unknown") -MESSAGE:New(text,5):ToGroup(self.group) -self:I(self.lid..text) -end -local position=self:GetCoordinate(nil,player.name) -local fc={} -for airbasename,_flightcontrol in pairs(_DATABASE.FLIGHTCONTROLS)do -local flightcontrol=_flightcontrol -local coord=flightcontrol:GetCoordinate() -local dist=coord:Get2DDistance(position) -table.insert(fc,{airbasename=airbasename,dist=dist}) -end -local function _sort(a,b) -return a.dist=1 then -local fsmstate=self:GetState() -local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName)or"N/A" -local skill=self.skill and tostring(self.skill)or"N/A" -local NassetsTot=#self.assets -local NassetsInS=self:CountAssets(true) -local NassetsQP=0;local NassetsP=0;local NassetsQ=0 -if self.legion then -NassetsQP,NassetsP,NassetsQ=self.legion:CountAssetsOnMission(nil,self) -end -local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]", -fsmstate,self.aircrafttype,callsign,skill,NassetsTot,NassetsInS,NassetsQP,NassetsP,NassetsQ) -self:T(self.lid..text) -if self.verbose>=3 and self.weaponData then -local text="Weapon Data:" -for bit,_weapondata in pairs(self.weaponData)do -local weapondata=_weapondata -text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km",bit,weapondata.RangeMin/1000,weapondata.RangeMax/1000) -end -self:I(self.lid..text) -end -self:_CheckAssetStatus() -end -if not self:IsStopped()then -self:__Status(-60) -end -end -INTEL={ -ClassName="INTEL", -verbose=0, -lid=nil, -alias=nil, -filterCategory={}, -detectionset=nil, -Contacts={}, -ContactsLost={}, -ContactsUnknown={}, -Clusters={}, -clustercounter=1, -clusterradius=15000, -clusteranalysis=true, -clustermarkers=false, -clusterarrows=false, -prediction=300, -detectStatics=false, -} -INTEL.Ctype={ -GROUND="Ground", -NAVAL="Naval", -AIRCRAFT="Aircraft", -STRUCTURE="Structure" -} -INTEL.version="0.3.6" -function INTEL:New(DetectionSet,Coalition,Alias) -local self=BASE:Inherit(self,FSM:New()) -self.detectionset=DetectionSet or SET_GROUP:New() -if Coalition and type(Coalition)=="string"then -if Coalition=="blue"then -Coalition=coalition.side.BLUE -elseif Coalition=="red"then -Coalition=coalition.side.RED -elseif Coalition=="neutral"then -Coalition=coalition.side.NEUTRAL -else -self:E("ERROR: Unknown coalition in INTEL!") -end -end -self.coalition=Coalition or DetectionSet:CountAlive()>0 and DetectionSet:GetFirst():GetCoalition()or nil -if self.coalition then -local coalitionname=UTILS.GetCoalitionName(self.coalition):lower() -self.detectionset:FilterCoalitions(coalitionname) -end -self.detectionset:FilterOnce() -if Alias then -self.alias=tostring(Alias) -else -self.alias="INTEL SPECTRE" -if self.coalition then -if self.coalition==coalition.side.RED then -self.alias="INTEL KGB" -elseif self.coalition==coalition.side.BLUE then -self.alias="INTEL CIA" -end -end -end -self.DetectVisual=true -self.DetectOptical=true -self.DetectRadar=true -self.DetectIRST=true -self.DetectRWR=true -self.DetectDLINK=true -self.statusupdate=-60 -self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","Status","*") -self:AddTransition("*","Stop","Stopped") -self:AddTransition("*","Detect","*") -self:AddTransition("*","NewContact","*") -self:AddTransition("*","LostContact","*") -self:AddTransition("*","NewCluster","*") -self:AddTransition("*","LostCluster","*") -self:SetForgetTime() -self:SetAcceptZones() -self:SetRejectZones() -self:SetConflictZones() -return self -end -function INTEL:SetAcceptZones(AcceptZoneSet) -self.acceptzoneset=AcceptZoneSet or SET_ZONE:New() -return self -end -function INTEL:AddAcceptZone(AcceptZone) -self.acceptzoneset:AddZone(AcceptZone) -return self -end -function INTEL:RemoveAcceptZone(AcceptZone) -self.acceptzoneset:Remove(AcceptZone:GetName(),true) -return self -end -function INTEL:SetRejectZones(RejectZoneSet) -self.rejectzoneset=RejectZoneSet or SET_ZONE:New() -return self -end -function INTEL:AddRejectZone(RejectZone) -self.rejectzoneset:AddZone(RejectZone) -return self -end -function INTEL:RemoveRejectZone(RejectZone) -self.rejectzoneset:Remove(RejectZone:GetName(),true) -return self -end -function INTEL:SetConflictZones(ConflictZoneSet) -self.conflictzoneset=ConflictZoneSet or SET_ZONE:New() -return self -end -function INTEL:AddConflictZone(ConflictZone) -self.conflictzoneset:AddZone(ConflictZone) -return self -end -function INTEL:RemoveConflictZone(ConflictZone) -self.conflictzoneset:Remove(ConflictZone:GetName(),true) -return self -end -function INTEL:SetForgetTime(TimeInterval) -return self -end -function INTEL:SetFilterCategory(Categories) -if type(Categories)~="table"then -Categories={Categories} -end -self.filterCategory=Categories -local text="Filter categories: " -for _,category in pairs(self.filterCategory)do -text=text..string.format("%d,",category) -end -self:T(self.lid..text) -return self -end -function INTEL:SetRadarBlur(minheight,thresheight,thresblur,closing) -self.RadarBlur=true -self.RadarBlurMinHeight=minheight or 250 -self.RadarBlurThresHeight=thresheight or 90 -self.RadarBlurThresBlur=thresblur or 85 -self.RadarBlurClosing=closing or 20 -self.RadarBlurClosingSquare=self.RadarBlurClosing*self.RadarBlurClosing -return self -end -function INTEL:SetAcceptRange(Range) -self.RadarAcceptRange=true -self.RadarAcceptRangeKilometers=Range or 75 -return self -end -function INTEL:FilterCategoryGroup(GroupCategories) -if type(GroupCategories)~="table"then -GroupCategories={GroupCategories} -end -self.filterCategoryGroup=GroupCategories -local text="Filter group categories: " -for _,category in pairs(self.filterCategoryGroup)do -text=text..string.format("%d,",category) -end -self:T(self.lid..text) -return self -end -function INTEL:AddAgent(AgentGroup) -if AgentGroup:IsInstanceOf("OPSGROUP")then -AgentGroup=AgentGroup:GetGroup() -end -self.detectionset:AddGroup(AgentGroup,true) -return self -end -function INTEL:SetClusterAnalysis(Switch,Markers,Arrows) -self.clusteranalysis=Switch -self.clustermarkers=Markers -self.clusterarrows=Arrows -return self -end -function INTEL:SetDetectStatics(Switch) -if Switch and Switch==true then -self.detectStatics=true -else -self.detectStatics=false -end -return self -end -function INTEL:SetVerbosity(Verbosity) -self.verbose=Verbosity or 2 -return self -end -function INTEL:AddMissionToContact(Contact,Mission) -if Mission and Contact then -Contact.mission=Mission -end -return self -end -function INTEL:AddMissionToCluster(Cluster,Mission) -if Mission and Cluster then -Cluster.mission=Mission -end -return self -end -function INTEL:SetClusterRadius(radius) -self.clusterradius=(radius or 15)*1000 -return self -end -function INTEL:SetDetectionTypes(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -self.DetectVisual=DetectVisual and true -self.DetectOptical=DetectOptical and true -self.DetectRadar=DetectRadar and true -self.DetectIRST=DetectIRST and true -self.DetectRWR=DetectRWR and true -self.DetectDLINK=DetectDLINK and true -return self -end -function INTEL:GetContactTable() -if self:Is("Running")then -return self.Contacts -else -return nil -end -end -function INTEL:GetClusterTable() -if self:Is("Running")and self.clusteranalysis then -return self.Clusters -else -return nil -end -end -function INTEL:GetContactName(Contact) -return Contact.groupname -end -function INTEL:GetContactGroup(Contact) -return Contact.group -end -function INTEL:GetContactThreatlevel(Contact) -return Contact.threatlevel -end -function INTEL:GetContactTypeName(Contact) -return Contact.typename -end -function INTEL:GetContactCategoryName(Contact) -return Contact.categoryname -end -function INTEL:GetContactCoordinate(Contact) -return Contact.position -end -function INTEL:onafterStart(From,Event,To) -local text=string.format("Starting INTEL v%s",self.version) -self:I(self.lid..text) -self:__Status(-math.random(10)) -return self -end -function INTEL:onafterStatus(From,Event,To) -local fsmstate=self:GetState() -self.ContactsLost={} -self.ContactsUnknown={} -self:UpdateIntel() -local Ncontacts=#self.Contacts -local Nclusters=#self.Clusters -if self.verbose>=1 then -local text=string.format("Status %s [Agents=%s]: Contacts=%d, Clusters=%d, New=%d, Lost=%d",fsmstate,self.detectionset:CountAlive(),Ncontacts,Nclusters,#self.ContactsUnknown,#self.ContactsLost) -self:I(self.lid..text) -end -if self.verbose>=2 and Ncontacts>0 then -local text="Detected Contacts:" -for _,_contact in pairs(self.Contacts)do -local contact=_contact -local dT=timer.getAbsTime()-contact.Tdetected -text=text..string.format("\n- %s (%s): %s, units=%d, T=%d sec",contact.categoryname,contact.attribute,contact.groupname,contact.isStatic and 1 or contact.group:CountAliveUnits(),dT) -if contact.mission then -local mission=contact.mission -text=text..string.format(" mission name=%s type=%s target=%s",mission.name,mission.type,mission:GetTargetName()or"unknown") -end -end -self:I(self.lid..text) -end -self:__Status(self.statusupdate) -return self -end -function INTEL:UpdateIntel() -local DetectedUnits={} -local RecceDetecting={} -for _,_group in pairs(self.detectionset.Set or{})do -local group=_group -if group and group:IsAlive()then -for _,_recce in pairs(group:GetUnits())do -local recce=_recce -self:GetDetectedUnits(recce,DetectedUnits,RecceDetecting,self.DetectVisual,self.DetectOptical,self.DetectRadar,self.DetectIRST,self.DetectRWR,self.DetectDLINK) -end -end -end -local remove={} -for unitname,_unit in pairs(DetectedUnits)do -local unit=_unit -local inconflictzone=false -if self.conflictzoneset:Count()>0 then -for _,_zone in pairs(self.conflictzoneset.Set)do -local zone=_zone -if unit:IsInZone(zone)then -inconflictzone=true -break -end -end -end -if self.acceptzoneset:Count()>0 then -local inzone=false -for _,_zone in pairs(self.acceptzoneset.Set)do -local zone=_zone -if unit:IsInZone(zone)then -inzone=true -break -end -end -if(not inzone)and(not inconflictzone)then -table.insert(remove,unitname) -end -end -if self.rejectzoneset:Count()>0 then -local inzone=false -for _,_zone in pairs(self.rejectzoneset.Set)do -local zone=_zone -if unit:IsInZone(zone)then -inzone=true -break -end -end -if inzone and(not inconflictzone)then -table.insert(remove,unitname) -end -end -if#self.filterCategory>0 and unit:IsInstanceOf("UNIT")then -local unitcategory=unit:GetUnitCategory() -local keepit=false -for _,filtercategory in pairs(self.filterCategory)do -if unitcategory==filtercategory then -keepit=true -break -end -end -if not keepit then -self:T(self.lid..string.format("Removing unit %s category=%d",unitname,unit:GetCategory())) -table.insert(remove,unitname) -end -end -end -for _,unitname in pairs(remove)do -DetectedUnits[unitname]=nil -end -local DetectedGroups={} -local DetectedStatics={} -local RecceGroups={} -for unitname,_unit in pairs(DetectedUnits)do -local unit=_unit -if unit:IsInstanceOf("UNIT")then -local group=unit:GetGroup() -if group then -local groupname=group:GetName() -DetectedGroups[groupname]=group -RecceGroups[groupname]=RecceDetecting[unitname] -end -else -if self.detectStatics then -DetectedStatics[unitname]=unit -RecceGroups[unitname]=RecceDetecting[unitname] -end -end -end -self:CreateDetectedItems(DetectedGroups,DetectedStatics,RecceGroups) -if self.clusteranalysis then -self:PaintPicture() -end -return self -end -function INTEL:_UpdateContact(Contact) -if Contact.isStatic then -else -if Contact.group and Contact.group:IsAlive()then -Contact.Tdetected=timer.getAbsTime() -Contact.position=Contact.group:GetCoordinate() -Contact.velocity=Contact.group:GetVelocityVec3() -Contact.speed=Contact.group:GetVelocityMPS() -if Contact.group:IsAir()then -Contact.altitude=Contact.group:GetAltitude() -local oldheading=Contact.heading or 1 -local newheading=Contact.group:GetHeading() -if newheading==0 then newheading=1 end -local changeh=math.abs(((oldheading-newheading)+360)%360) -Contact.heading=newheading -if changeh>10 then -Contact.maneuvering=true -else -Contact.maneuvering=false -end -end -end -end -return self -end -function INTEL:_CreateContact(Positionable,RecceName) -if Positionable and Positionable:IsAlive()then -local item={} -if Positionable:IsInstanceOf("GROUP")then -local group=Positionable -item.groupname=group:GetName() -item.group=group -item.Tdetected=timer.getAbsTime() -item.typename=group:GetTypeName() -item.attribute=group:GetAttribute() -item.category=group:GetCategory() -item.categoryname=group:GetCategoryName() -item.threatlevel=group:GetThreatLevel() -item.position=group:GetCoordinate() -item.velocity=group:GetVelocityVec3() -item.speed=group:GetVelocityMPS() -item.recce=RecceName -item.isground=group:IsGround()or false -item.isship=group:IsShip()or false -item.isStatic=false -if group:IsAir()then -item.platform=group:GetNatoReportingName() -item.heading=group:GetHeading() -item.maneuvering=false -item.altitude=group:GetAltitude() -else -item.platform="Unknown" -item.altitude=group:GetAltitude(true) -end -if item.category==Group.Category.AIRPLANE or item.category==Group.Category.HELICOPTER then -item.ctype=INTEL.Ctype.AIRCRAFT -elseif item.category==Group.Category.GROUND or item.category==Group.Category.TRAIN then -item.ctype=INTEL.Ctype.GROUND -elseif item.category==Group.Category.SHIP then -item.ctype=INTEL.Ctype.NAVAL -end -return item -elseif Positionable:IsInstanceOf("STATIC")then -local static=Positionable -item.groupname=static:GetName() -item.group=static -item.Tdetected=timer.getAbsTime() -item.typename=static:GetTypeName()or"Unknown" -item.attribute="Static" -item.category=3 -item.categoryname=static:GetCategoryName()or"Unknown" -item.threatlevel=static:GetThreatLevel()or 0 -item.position=static:GetCoordinate() -item.velocity=static:GetVelocityVec3() -item.speed=0 -item.recce=RecceName -item.isground=true -item.isship=false -item.isStatic=true -item.ctype=INTEL.Ctype.STRUCTURE -return item -else -self:E(self.lid..string.format("ERROR: object needs to be a GROUP or STATIC!")) -end -end -return nil -end -function INTEL:CreateDetectedItems(DetectedGroups,DetectedStatics,RecceDetecting) -self:F({RecceDetecting=RecceDetecting}) -local Tnow=timer.getAbsTime() -for groupname,_group in pairs(DetectedGroups)do -local group=_group -self:KnowObject(group,RecceDetecting[groupname]) -end -for staticname,_static in pairs(DetectedStatics)do -local static=_static -self:KnowObject(static,RecceDetecting[staticname]) -end -for i=#self.Contacts,1,-1 do -local item=self.Contacts[i] -if self:_CheckContactLost(item)then -self:LostContact(item) -self:RemoveContact(item) -end -end -return self -end -function INTEL:GetDetectedUnits(Unit,DetectedUnits,RecceDetecting,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -local reccename=Unit:GetName() -local detectedtargets=Unit:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -for DetectionObjectID,Detection in pairs(detectedtargets or{})do -local DetectedObject=Detection.object -if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then -local status,name=pcall( -function() -local name=DetectedObject:getName() -return name -end) -if status then -local unit=UNIT:FindByName(name) -if unit and unit:IsAlive()then -local DetectionAccepted=true -if self.RadarAcceptRange then -local reccecoord=Unit:GetCoordinate() -local coord=unit:GetCoordinate() -local dist=math.floor(coord:Get2DDistance(reccecoord)/1000) -if dist>self.RadarAcceptRangeKilometers then DetectionAccepted=false end -end -if self.RadarBlur then -local reccecoord=Unit:GetCoordinate() -local coord=unit:GetCoordinate() -local dist=math.floor(coord:Get2DDistance(reccecoord)/1000) -local AGL=unit:GetAltitude(true) -local minheight=self.RadarBlurMinHeight or 250 -local thresheight=self.RadarBlurThresHeight or 90 -local thresblur=self.RadarBlurThresBlur or 85 -if dist<=self.RadarBlurClosing then -thresheight=(((dist*dist)/self.RadarBlurClosingSquare)*thresheight) -thresblur=(((dist*dist)/self.RadarBlurClosingSquare)*thresblur) -end -local fheight=math.floor(math.random(1,10000)/100) -local fblur=math.floor(math.random(1,10000)/100) -if fblur>thresblur then DetectionAccepted=false end -if AGL<=minheight and fheight1 then -MESSAGE:New("Radar Blur",10):ToLogIf(self.debug):ToAllIf(self.verbose>1) -MESSAGE:New("Unit "..name.." is at "..math.floor(AGL).."m. Distance "..math.floor(dist).."km.",10):ToLogIf(self.debug):ToAllIf(self.verbose>1) -MESSAGE:New(string.format("fheight = %d/%d | fblur = %d/%d",fheight,thresheight,fblur,thresblur),10):ToLogIf(self.debug):ToAllIf(self.verbose>1) -MESSAGE:New("Detection Accepted = "..tostring(DetectionAccepted),10):ToLogIf(self.debug):ToAllIf(self.verbose>1) -end -end -if DetectionAccepted then -DetectedUnits[name]=unit -RecceDetecting[name]=reccename -self:T(string.format("Unit %s detect by %s",name,reccename)) -end -else -if self.detectStatics then -local static=STATIC:FindByName(name,false) -if static then -DetectedUnits[name]=static -RecceDetecting[name]=reccename -end -end -end -else -self:T(self.lid..string.format("WARNING: Could not get name of detected object ID=%s! Detected by %s",DetectedObject.id_,reccename)) -end -end -end -end -function INTEL:onafterNewContact(From,Event,To,Contact) -self:F(self.lid..string.format("NEW contact %s",Contact.groupname)) -table.insert(self.ContactsUnknown,Contact) -return self -end -function INTEL:onafterLostContact(From,Event,To,Contact) -self:F(self.lid..string.format("LOST contact %s",Contact.groupname)) -table.insert(self.ContactsLost,Contact) -return self -end -function INTEL:onafterNewCluster(From,Event,To,Cluster) -self:F(self.lid..string.format("NEW cluster #%d [%s] of size %d",Cluster.index,Cluster.ctype,Cluster.size)) -self:_AddCluster(Cluster) -return self -end -function INTEL:onafterLostCluster(From,Event,To,Cluster,Mission) -local text=self.lid..string.format("LOST cluster #%d [%s]",Cluster.index,Cluster.ctype) -if Mission then -local mission=Mission -text=text..string.format(" mission name=%s type=%s target=%s",mission.name,mission.type,mission:GetTargetName()or"unknown") -end -self:T(text) -return self -end -function INTEL:KnowObject(Positionable,RecceName,Tdetected) -local Tnow=timer.getAbsTime() -Tdetected=Tdetected or Tnow -if Positionable and Positionable:IsAlive()then -if Tdetected>Tnow then -self:ScheduleOnce(Tdetected-Tnow,self.KnowObject,self,Positionable,RecceName) -else -local name=Positionable:GetName() -local contact=self:GetContactByName(name) -if contact then -self:_UpdateContact(contact) -else -contact=self:_CreateContact(Positionable,RecceName) -if contact then -self:T(string.format("%s contact detected by %s",contact.groupname,RecceName or"unknown")) -self:AddContact(contact) -self:NewContact(contact) -end -end -end -end -return self -end -function INTEL:GetContactByName(groupname) -for i,_contact in pairs(self.Contacts)do -local contact=_contact -if contact.groupname==groupname then -return contact -end -end -return nil -end -function INTEL:_IsContactKnown(Contact) -for i,_contact in pairs(self.Contacts)do -local contact=_contact -if contact.groupname==Contact.groupname then -return true -end -end -return false -end -function INTEL:AddContact(Contact) -if self:_IsContactKnown(Contact)then -self:E(self.lid..string.format("WARNING: Contact %s is already in the contact table!",tostring(Contact.groupname))) -else -self:T(self.lid..string.format("Adding new Contact %s to table",tostring(Contact.groupname))) -table.insert(self.Contacts,Contact) -end -return self -end -function INTEL:RemoveContact(Contact) -for i,_contact in pairs(self.Contacts)do -local contact=_contact -if contact.groupname==Contact.groupname then -table.remove(self.Contacts,i) -end -end -return self -end -function INTEL:_CheckContactLost(Contact) -if Contact.group==nil or not Contact.group:IsAlive()then -return true -end -if Contact.isStatic then -return false -end -local dT=timer.getAbsTime()-Contact.Tdetected -local dTforget=nil -if Contact.category==Group.Category.GROUND then -dTforget=60*60*2 -elseif Contact.category==Group.Category.AIRPLANE then -dTforget=60*10 -elseif Contact.category==Group.Category.HELICOPTER then -dTforget=60*20 -elseif Contact.category==Group.Category.SHIP then -dTforget=60*60 -elseif Contact.category==Group.Category.TRAIN then -dTforget=60*60 -end -if dT>dTforget then -return true -else -return false -end -end -function INTEL:PaintPicture() -self:F(self.lid.."Painting Picture!") -for _,_contact in pairs(self.ContactsLost)do -local contact=_contact -local cluster=self:GetClusterOfContact(contact) -if cluster then -self:RemoveContactFromCluster(contact,cluster) -end -end -local ClusterSet={} -for _i,_cluster in pairs(self.Clusters)do -local cluster=_cluster -if cluster.size>0 and self:ClusterCountUnits(cluster)>0 then -table.insert(ClusterSet,_cluster) -else -if cluster.marker then -cluster.marker:Remove() -end -if cluster.markerID then -COORDINATE:RemoveMark(cluster.markerID) -end -self:LostCluster(cluster,cluster.mission) -end -end -self.Clusters=ClusterSet -self:_UpdateClusterPositions() -for _,_contact in pairs(self.Contacts)do -local contact=_contact -self:T(string.format("Paint Picture: checking for %s",contact.groupname)) -local currentcluster=self:GetClusterOfContact(contact) -if currentcluster then -local isconnected=self:IsContactConnectedToCluster(contact,currentcluster) -if isconnected then -else -self:RemoveContactFromCluster(contact,currentcluster) -local cluster=self:_GetClosestClusterOfContact(contact) -if cluster then -self:AddContactToCluster(contact,cluster) -else -local newcluster=self:_CreateClusterFromContact(contact) -self:NewCluster(newcluster) -end -end -else -self:T(self.lid..string.format("Paint Picture: contact %s has NO current cluster",contact.groupname)) -local cluster=self:_GetClosestClusterOfContact(contact) -if cluster then -self:T(self.lid..string.format("Paint Picture: contact %s has closest cluster #%d",contact.groupname,cluster.index)) -self:AddContactToCluster(contact,cluster) -else -self:T(self.lid..string.format("Paint Picture: contact %s has no closest cluster ==> Create new cluster",contact.groupname)) -local newcluster=self:_CreateClusterFromContact(contact) -self:NewCluster(newcluster) -end -end -end -self:_UpdateClusterPositions() -if self.clustermarkers then -for _,_cluster in pairs(self.Clusters)do -local cluster=_cluster -if self.verbose>=1 then -BASE:I("Updating cluster marker and future position") -end -self:UpdateClusterMarker(cluster) -self:CalcClusterFuturePosition(cluster,300) -end -end -return self -end -function INTEL:_CreateCluster() -local cluster={} -cluster.index=self.clustercounter -cluster.coordinate=COORDINATE:New(0,0,0) -cluster.threatlevelSum=0 -cluster.threatlevelMax=0 -cluster.size=0 -cluster.Contacts={} -cluster.altitude=0 -self.clustercounter=self.clustercounter+1 -return cluster -end -function INTEL:_CreateClusterFromContact(Contact) -local cluster=self:_CreateCluster() -self:T(self.lid..string.format("Created NEW cluster #%d with first contact %s",cluster.index,Contact.groupname)) -cluster.coordinate:UpdateFromCoordinate(Contact.position) -cluster.ctype=Contact.ctype -self:AddContactToCluster(Contact,cluster) -return cluster -end -function INTEL:_AddCluster(Cluster) -table.insert(self.Clusters,Cluster) -return self -end -function INTEL:AddContactToCluster(contact,cluster) -if contact and cluster then -table.insert(cluster.Contacts,contact) -cluster.threatlevelSum=cluster.threatlevelSum+contact.threatlevel -cluster.size=cluster.size+1 -self:GetClusterAltitude(cluster,true) -self:T(self.lid..string.format("Adding contact %s to cluster #%d [%s] ==> New size=%d",contact.groupname,cluster.index,cluster.ctype,cluster.size)) -end -return self -end -function INTEL:RemoveContactFromCluster(contact,cluster) -if contact and cluster then -for i=#cluster.Contacts,1,-1 do -local Contact=cluster.Contacts[i] -if Contact.groupname==contact.groupname then -cluster.threatlevelSum=cluster.threatlevelSum-contact.threatlevel -cluster.size=cluster.size-1 -table.remove(cluster.Contacts,i) -self:T(self.lid..string.format("Removing contact %s from cluster #%d ==> New cluster size=%d",contact.groupname,cluster.index,cluster.size)) -return self -end -end -end -return self -end -function INTEL:CalcClusterThreatlevelSum(cluster) -local threatlevel=0 -for _,_contact in pairs(cluster.Contacts)do -local contact=_contact -threatlevel=threatlevel+contact.threatlevel -end -cluster.threatlevelSum=threatlevel -return threatlevel -end -function INTEL:CalcClusterThreatlevelAverage(cluster) -local threatlevel=self:CalcClusterThreatlevelSum(cluster) -threatlevel=threatlevel/cluster.size -cluster.threatlevelAve=threatlevel -return threatlevel -end -function INTEL:CalcClusterThreatlevelMax(cluster) -local threatlevel=0 -for _,_contact in pairs(cluster.Contacts)do -local contact=_contact -if contact.threatlevel>threatlevel then -threatlevel=contact.threatlevel -end -end -cluster.threatlevelMax=threatlevel -return threatlevel -end -function INTEL:CalcClusterDirection(cluster) -local direction=0 -local speedsum=0 -local n=0 -for _,_contact in pairs(cluster.Contacts)do -local contact=_contact -if(not contact.isStatic)and contact.group:IsAlive()then -local speed=contact.group:GetVelocityKNOTS() -direction=direction+(contact.group:GetHeading()*speed) -n=n+1 -speedsum=speedsum+speed -end -end -if n==0 then -return 0 -else -return math.floor(direction/(speedsum*n)) -end -end -function INTEL:CalcClusterSpeed(cluster) -local velocity=0;local n=0 -for _,_contact in pairs(cluster.Contacts)do -local contact=_contact -if(not contact.isStatic)and contact.group:IsAlive()then -velocity=velocity+contact.group:GetVelocityMPS() -n=n+1 -end -end -if n==0 then -return 0 -else -return math.floor(velocity/n) -end -end -function INTEL:CalcClusterVelocityVec3(cluster) -local v={x=0,y=0,z=0} -for _,_contact in pairs(cluster.Contacts)do -local contact=_contact -if(not contact.isStatic)and contact.group:IsAlive()then -local vec=contact.group:GetVelocityVec3() -v.x=v.x+vec.x -v.y=v.y+vec.y -v.z=v.y+vec.z -end -end -return v -end -function INTEL:CalcClusterFuturePosition(cluster,seconds) -local p=self:GetClusterCoordinate(cluster) -local v=self:CalcClusterVelocityVec3(cluster) -local t=seconds or self.prediction -local Vec3={x=p.x+v.x*t,y=p.y+v.y*t,z=p.z+v.z*t} -local futureposition=COORDINATE:NewFromVec3(Vec3) -if self.clustermarkers and self.clusterarrows then -if cluster.markerID then -COORDINATE:RemoveMark(cluster.markerID) -end -cluster.markerID=p:ArrowToAll(futureposition,self.coalition,{1,0,0},1,{1,1,0},0.5,2,true,"Position Calc") -end -return futureposition -end -function INTEL:CheckContactInClusters(contact) -for _,_cluster in pairs(self.Clusters)do -local cluster=_cluster -for _,_contact in pairs(cluster.Contacts)do -local Contact=_contact -if Contact.groupname==contact.groupname then -return true -end -end -end -return false -end -function INTEL:IsContactConnectedToCluster(contact,cluster) -if contact.ctype~=cluster.ctype then -return false,math.huge -end -for _,_contact in pairs(cluster.Contacts)do -local Contact=_contact -if Contact.groupname~=contact.groupname or cluster.size==1 then -local dist=Contact.position:DistanceFromPointVec2(contact.position) -local airprox=true -if contact.ctype==INTEL.Ctype.AIRCRAFT then -self:T(string.format("Cluster Alt=%d | Contact Alt=%d",cluster.altitude,contact.altitude)) -local adist=math.abs(cluster.altitude-contact.altitude) -if adist>UTILS.FeetToMeters(10000)then -airprox=false -end -end -if distUTILS.FeetToMeters(10000)then -airprox=false -end -end -if dist0 then -avgalt=newalt/n -end -Cluster.altitude=avgalt -self:T(string.format("Updating Cluster Altitude: %d",Cluster.altitude)) -return Cluster.altitude -end -function INTEL:GetClusterCoordinate(Cluster,Update) -local x=0;local y=0;local z=0;local n=0 -for _,_contact in pairs(Cluster.Contacts)do -local contact=_contact -local vec3=nil -if Update and contact.group and contact.group:IsAlive()then -vec3=contact.group:GetVec3() -end -if not vec3 then -vec3=contact.position -end -if vec3 then -x=x+vec3.x -y=y+vec3.y -z=z+vec3.z -n=n+1 -end -end -if n>0 then -local Vec3={x=x/n,y=y/n,z=z/n} -Cluster.coordinate:UpdateFromVec3(Vec3) -end -return Cluster.coordinate -end -function INTEL:_CheckClusterCoordinateChanged(Cluster,Coordinate,Threshold) -Threshold=Threshold or 100 -Coordinate=Coordinate or Cluster.coordinate -local a=Coordinate:GetVec3() -local b=self:GetClusterCoordinate(Cluster,true):GetVec3() -local dist=UTILS.VecDist3D(a,b) -if dist>Threshold then -return true -else -return false -end -end -function INTEL:_UpdateClusterPositions() -for _,_cluster in pairs(self.Clusters)do -local cluster=_cluster -local coord=self:GetClusterCoordinate(cluster,true) -local alt=self:GetClusterAltitude(cluster,true) -self:T(self.lid..string.format("Updating Cluster position size: %s",cluster.size)) -end -return self -end -function INTEL:ContactCountUnits(Contact) -if Contact.isStatic then -if Contact.group and Contact.group:IsAlive()then -return 1 -else -return 0 -end -else -if Contact.group then -local n=Contact.group:CountAliveUnits() -return n -else -return 0 -end -end -end -function INTEL:ClusterCountUnits(Cluster) -local unitcount=0 -for _,_contact in pairs(Cluster.Contacts)do -local contact=_contact -unitcount=unitcount+self:ContactCountUnits(contact) -end -return unitcount -end -function INTEL:UpdateClusterMarker(cluster) -local unitcount=self:ClusterCountUnits(cluster) -local text=string.format("Cluster #%d: %s\nSize %d\nUnits %d\nTLsum=%d",cluster.index,cluster.ctype,cluster.size,unitcount,cluster.threatlevelSum) -if not cluster.marker then -cluster.marker=MARKER:New(cluster.coordinate,text):ToCoalition(self.coalition) -else -local refresh=false -if cluster.marker.text~=text then -cluster.marker.text=text -refresh=true -end -local coordchange=self:_CheckClusterCoordinateChanged(cluster,cluster.marker.coordinate) -if coordchange then -cluster.marker.coordinate:UpdateFromCoordinate(cluster.coordinate) -refresh=true -end -if refresh then -cluster.marker:Refresh() -end -end -return self -end -function INTEL:GetHighestThreatContact(Cluster) -local threatlevel=-1 -local rcontact=nil -for _,_contact in pairs(Cluster.Contacts)do -local contact=_contact -if contact.threatlevel>threatlevel then -threatlevel=contact.threatlevel -rcontact=contact -end -end -return rcontact -end -INTEL_DLINK={ -ClassName="INTEL_DLINK", -verbose=0, -lid=nil, -alias=nil, -cachetime=300, -interval=20, -contacts={}, -clusters={}, -contactcoords={}, -} -INTEL_DLINK.version="0.0.1" -function INTEL_DLINK:New(Intels,Alias,Interval,Cachetime) -local self=BASE:Inherit(self,FSM:New()) -self.intels=Intels or{} -self.contacts={} -self.clusters={} -self.contactcoords={} -if Alias then -self.alias=tostring(Alias) -else -self.alias="SPECTRE" -end -self.cachetime=Cachetime or 300 -self.interval=Interval or 20 -self.lid=string.format("INTEL_DLINK %s | ",self.alias) -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","Collect","*") -self:AddTransition("*","Collected","*") -self:AddTransition("*","Stop","Stopped") -return self -end -function INTEL_DLINK:AddIntel(Intel) -self:T(self.lid.."AddIntel") -if Intel then -table.insert(self.intels,Intel) -end -return self -end -function INTEL_DLINK:onafterStart(From,Event,To) -self:T({From,Event,To}) -local text=string.format("Version %s started.",self.version) -self:I(self.lid..text) -self:__Collect(-math.random(1,10)) -return self -end -function INTEL_DLINK:onbeforeCollect(From,Event,To) -self:T({From,Event,To}) -self:T("Contacts Data Gathering") -local newcontacts={} -local intels=self.intels -for _,_intel in pairs(intels)do -_intel=_intel -if _intel:Is("Running")then -local ctable=_intel:GetContactTable()or{} -for _,_contact in pairs(ctable)do -local _ID=string.format("%s-%d",_contact.groupname,_contact.Tdetected) -self:T(string.format("Adding %s",_ID)) -newcontacts[_ID]=_contact -end -end -end -self:T("Cleanup") -local contacttable={} -local coordtable={} -local TNow=timer.getAbsTime() -local Tcache=self.cachetime -for _ind,_contact in pairs(newcontacts)do -if TNow-_contact.Tdetected0 then -self:ScheduleOnce(Delay,LEGION.RelocateCohort,self,Cohort,Legion,0,NcarriersMin,NcarriersMax,TransportLegions) -else -if Legion:IsCohort(Cohort.name)then -self:E(self.lid..string.format("ERROR: Cohort %s is already part of new legion %s ==> CANNOT Relocate!",Cohort.name,Legion.alias)) -return self -else -table.insert(Legion.cohorts,Cohort) -end -if not self:IsCohort(Cohort.name)then -self:E(self.lid..string.format("ERROR: Cohort %s is NOT part of this legion %s ==> CANNOT Relocate!",Cohort.name,self.alias)) -return self -end -if self.alias==Legion.alias then -self:E(self.lid..string.format("ERROR: old legion %s is same as new legion %s ==> CANNOT Relocate!",self.alias,Legion.alias)) -return self -end -Cohort:Relocate() -local mission=AUFTRAG:_NewRELOCATECOHORT(Legion,Cohort) -if false then -mission:_AddAssets(Cohort.assets) -self:I(self.lid..string.format("Relocating Cohort %s [nassets=%d] to legion %s",Cohort.name,#Cohort.assets,Legion.alias)) -self:MissionAssign(mission,{self}) -else -mission:AssignCohort(Cohort) -mission:SetRequiredAssets(#Cohort.assets) -if NcarriersMin and NcarriersMin>0 then -mission:SetRequiredTransport(Legion.spawnzone,NcarriersMin,NcarriersMax) -end -if TransportLegions then -for _,legion in pairs(TransportLegions)do -mission:AssignTransportLegion(legion) -end -end -mission:SetMissionRange(10000) -self:AddMission(mission) -end -end -return self -end -function LEGION:_GetCohort(CohortName) -for _,_cohort in pairs(self.cohorts)do -local cohort=_cohort -if cohort.name==CohortName then -return cohort -end -end -return nil -end -function LEGION:IsCohort(CohortName) -for _,_cohort in pairs(self.cohorts)do -local cohort=_cohort -if cohort.name==CohortName then -return true -end -end -return false -end -function LEGION:GetName() -return self.alias -end -function LEGION:_GetCohortOfAsset(Asset) -local cohort=self:_GetCohort(Asset.squadname) -return cohort -end -function LEGION:IsBrigade() -local is=self.ClassName==BRIGADE.ClassName -return is -end -function LEGION:IsAirwing() -local is=self.ClassName==AIRWING.ClassName -return is -end -function LEGION:IsFleet() -local is=self.ClassName==FLEET.ClassName -return is -end -function LEGION:onafterStart(From,Event,To) -self:GetParent(self,LEGION).onafterStart(self,From,Event,To) -self:T3(self.lid..string.format("Starting LEGION v%s",LEGION.version)) -end -function LEGION:CheckMissionQueue() -local Nmissions=#self.missionqueue -if Nmissions==0 then -return nil -end -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if mission:IsNotOver()and mission:IsReadyToCancel()then -mission:Cancel() -end -end -if self:IsAirwing()then -if self:IsRunwayOperational()==false then -return nil -end -local airboss=self.airboss -if airboss then -if not airboss:IsIdle()then -return nil -end -end -end -local function _sort(a,b) -local taskA=a -local taskB=b -return(taskA.prio0 then -local N=mission.Nassigned-mission.Ndead -if N Reinforce=%s", -mission.reinforce,N,mission.Nassigned,mission.Ndead,mission.NassetsMin,tostring(reinforce))) -end -if(mission:IsQueued(self)or reinforce)and mission:IsReadyToGo()and(mission.importance==nil or mission.importance<=vip)then -local recruited,assets,legions=self:RecruitAssetsForMission(mission) -if recruited then -local EscortAvail=self:RecruitAssetsForEscort(mission,assets) -local TransportAvail=true -if EscortAvail then -local Transport=nil -if mission.NcarriersMin then -local Legions=mission.transportLegions or{self} -TransportAvail,Transport=self:AssignAssetsForTransport(Legions,assets,mission.NcarriersMin,mission.NcarriersMax,mission.transportDeployZone,mission.transportDisembarkZone,mission.carrierCategories,mission.carrierAttributes,mission.carrierProperties) -end -if TransportAvail and Transport then -mission.opstransport=Transport -end -end -if EscortAvail and TransportAvail then -self:MissionRequest(mission,assets) -if reinforce then -mission.reinforce=mission.reinforce-#assets -self:I(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce)) -end -return true -else -LEGION.UnRecruitAssets(assets,mission) -end -end -end -end -return nil -end -function LEGION:CheckTransportQueue() -local Ntransports=#self.transportqueue -if Ntransports==0 then -return nil -end -local function _sort(a,b) -local taskA=a -local taskB=b -return(taskA.prio0 then -for i,_asset in pairs(Assetlist)do -local asset=_asset -asset.requested=true -if asset.spawned then -asset.requested=false -end -asset.isReserved=false -if Mission.missionTask then -asset.missionTask=Mission.missionTask -end -if Mission.type==AUFTRAG.Type.ALERT5 then -asset.takeoffType=COORDINATE.WaypointType.TakeOffParking -end -Mission:AddAsset(asset) -end -local assignment=string.format("Mission-%d",Mission.auftragsnummer) -local request=self:_AddRequest(WAREHOUSE.Descriptor.ASSETLIST,Assetlist,#Assetlist,Mission.prio,assignment) -self:T(self.lid..string.format("Added request=%d for Nasssets=%d",request.uid,#Assetlist)) -Mission:_SetRequestID(self,self.queueid) -self:T(self.lid..string.format("Mission %s [%s] got Request ID=%d",Mission:GetName(),Mission:GetType(),self.queueid)) -if request then -if self:IsShip()then -self:T(self.lid.."Warehouse physical structure is SHIP. Requestes assets will be late activated!") -request.lateActivation=true -end -end -end -end -function LEGION:onafterTransportAssign(From,Event,To,Transport,Legions) -for _,_Legion in pairs(Legions)do -local Legion=_Legion -self:T(self.lid..string.format("Assigning transport %d to legion %s",Transport.uid,Legion.alias)) -Legion:AddOpsTransport(Transport) -Legion:TransportRequest(Transport) -end -end -function LEGION:onafterTransportRequest(From,Event,To,OpsTransport) -local AssetList={} -for i,_asset in pairs(OpsTransport.assets)do -local asset=_asset -if asset.wid==self.uid then -asset.requested=true -asset.isReserved=false -asset.missionTask=ENUMS.MissionTask.TRANSPORT -table.insert(AssetList,asset) -end -end -if#AssetList>0 then -OpsTransport:Requested() -OpsTransport:SetLegionStatus(self,OPSTRANSPORT.Status.REQUESTED) -local assignment=string.format("Transport-%d",OpsTransport.uid) -self:_AddRequest(WAREHOUSE.Descriptor.ASSETLIST,AssetList,#AssetList,OpsTransport.prio,assignment) -OpsTransport.requestID[self.alias]=self.queueid -end -end -function LEGION:onafterTransportCancel(From,Event,To,Transport) -self:T(self.lid..string.format("Cancel transport UID=%d",Transport.uid)) -Transport:SetLegionStatus(self,OPSTRANSPORT.Status.CANCELLED) -for i=#Transport.assets,1,-1 do -local asset=Transport.assets[i] -if asset.wid==self.uid then -local opsgroup=asset.flightgroup -if opsgroup then -opsgroup:TransportCancel(Transport) -end -local cargos=Transport:GetCargoOpsGroups(false) -for _,_cargo in pairs(cargos)do -local cargo=_cargo -cargo:_DelMyLift(Transport) -local legion=cargo.legion -if legion then -legion:T(self.lid..string.format("Adding cargo group %s back to legion",cargo:GetName())) -legion:__AddAsset(0.1,cargo.group,1) -end -end -Transport:DelAsset(asset) -asset.requested=nil -asset.isReserved=nil -end -end -if Transport.requestID[self.alias]then -self:_DeleteQueueItemByID(Transport.requestID[self.alias],self.queue) -end -end -function LEGION:onafterMissionCancel(From,Event,To,Mission) -self:T(self.lid..string.format("Cancel mission %s",Mission.name)) -Mission:SetLegionStatus(self,AUFTRAG.Status.CANCELLED) -for i=#Mission.assets,1,-1 do -local asset=Mission.assets[i] -if asset.wid==self.uid then -local opsgroup=asset.flightgroup -if opsgroup then -opsgroup:MissionCancel(Mission) -end -Mission:DelAsset(asset) -asset.requested=nil -asset.isReserved=nil -end -end -local requestID=Mission:_GetRequestID(self) -if requestID then -self:_DeleteQueueItemByID(requestID,self.queue) -end -end -function LEGION:onafterOpsOnMission(From,Event,To,OpsGroup,Mission) -self:T2(self.lid..string.format("Group %s on mission %s [%s]",OpsGroup:GetName(),Mission:GetName(),Mission:GetType())) -if self:IsAirwing()then -self:FlightOnMission(OpsGroup,Mission) -elseif self:IsBrigade()then -self:ArmyOnMission(OpsGroup,Mission) -else -self:NavyOnMission(OpsGroup,Mission) -end -if self:IsBrigade()and self:IsShip()then -OpsGroup:PauseMission() -self.warehouseOpsGroup:Load(OpsGroup,self.warehouseOpsElement) -end -if self.chief then -self.chief:OpsOnMission(OpsGroup,Mission) -end -if self.commander then -self.commander:OpsOnMission(OpsGroup,Mission) -end -end -function LEGION:onafterNewAsset(From,Event,To,asset,assignment) -self:GetParent(self,LEGION).onafterNewAsset(self,From,Event,To,asset,assignment) -local text=string.format("New asset %s with assignment %s and request assignment %s",asset.spawngroupname,tostring(asset.assignment),tostring(assignment)) -self:T(self.lid..text) -local cohort=self:_GetCohort(asset.assignment) -if cohort then -if asset.assignment==assignment then -local nunits=#asset.template.units -local text=string.format("Adding asset to cohort %s: assignment=%s, type=%s, attribute=%s, nunits=%d ngroup=%s",cohort.name,assignment,asset.unittype,asset.attribute,nunits,tostring(cohort.ngrouping)) -self:T(self.lid..text) -if cohort.ngrouping then -local template=asset.template -local N=math.max(#template.units,cohort.ngrouping) -asset.weight=0 -asset.cargobaytot=0 -for i=1,N do -local unit=template.units[i] -if i>nunits then -table.insert(template.units,UTILS.DeepCopy(template.units[1])) -asset.cargobaytot=asset.cargobaytot+asset.cargobay[1] -asset.weight=asset.weight+asset.weights[1] -template.units[i].x=template.units[1].x+5*(i-nunits) -template.units[i].y=template.units[1].y+5*(i-nunits) -else -if i<=cohort.ngrouping then -asset.weight=asset.weight+asset.weights[i] -asset.cargobaytot=asset.cargobaytot+asset.cargobay[i] -end -end -if i>cohort.ngrouping then -template.units[i]=nil -end -end -asset.nunits=cohort.ngrouping -self:T(self.lid..string.format("After regrouping: Nunits=%d, weight=%.1f cargobaytot=%.1f kg",#asset.template.units,asset.weight,asset.cargobaytot)) -end -asset.takeoffType=cohort.takeoffType~=nil and cohort.takeoffType or self.takeoffType -asset.parkingIDs=cohort.parkingIDs -cohort:GetCallsign(asset) -cohort:GetModex(asset) -asset.spawngroupname=string.format("%s_AID-%d",cohort.name,asset.uid) -cohort:AddAsset(asset) -else -self:T(self.lid..string.format("Asset returned to legion ==> calling LegionAssetReturned event")) -asset.takeoffType=cohort.takeoffType -self:LegionAssetReturned(cohort,asset) -end -end -end -function LEGION:onafterLegionAssetReturned(From,Event,To,Cohort,Asset) -self:T(self.lid..string.format("Asset %s from Cohort %s returned! asset.assignment=\"%s\"",Asset.spawngroupname,Cohort.name,tostring(Asset.assignment))) -if Asset.flightgroup and not Asset.flightgroup:IsStopped()then -Asset.flightgroup:Stop() -end -if Asset.flightgroup:IsFlightgroup()then -self:ReturnPayloadFromAsset(Asset) -end -if Asset.tacan then -Cohort:ReturnTacan(Asset.tacan) -end -Asset.Treturned=timer.getAbsTime() -end -function LEGION:onafterAssetSpawned(From,Event,To,group,asset,request) -self:T({From,Event,To,group:GetName(),asset.assignment,request.assignment}) -self:GetParent(self,LEGION).onafterAssetSpawned(self,From,Event,To,group,asset,request) -local cohort=self:_GetCohortOfAsset(asset) -if cohort then -self:T(self.lid..string.format("Cohort asset spawned %s",asset.spawngroupname)) -local flightgroup=self:_CreateFlightGroup(asset) -asset.flightgroup=flightgroup -asset.requested=nil -asset.Treturned=nil -local Tacan=cohort:FetchTacan() -if Tacan then -asset.tacan=Tacan -flightgroup:SwitchTACAN(Tacan,Morse,UnitName,Band) -end -local radioFreq,radioModu=cohort:GetRadio() -if radioFreq then -flightgroup:SwitchRadio(radioFreq,radioModu) -end -if cohort.fuellow then -flightgroup:SetFuelLowThreshold(cohort.fuellow) -end -if cohort.fuellowRefuel then -flightgroup:SetFuelLowRefuel(cohort.fuellowRefuel) -end -local assignment=request.assignment -if self:IsFleet()then -flightgroup:SetPathfinding(self.pathfinding) -end -if string.find(assignment,"Mission-")then -local uid=UTILS.Split(assignment,"-")[2] -local mission=self:GetMissionByID(uid) -local despawnLanding=cohort.despawnAfterLanding~=nil and cohort.despawnAfterLanding or self.despawnAfterLanding -if despawnLanding then -flightgroup:SetDespawnAfterLanding() -end -local despawnHolding=cohort.despawnAfterHolding~=nil and cohort.despawnAfterHolding or self.despawnAfterHolding -if despawnHolding then -flightgroup:SetDespawnAfterHolding() -end -if mission then -if Tacan then -end -flightgroup:AddMission(mission) -if self:IsBrigade()or self:IsFleet()then -flightgroup:SetReturnOnOutOfAmmo() -end -self:__OpsOnMission(5,flightgroup,mission) -else -if Tacan then -end -end -local chief=self.chief or(self.commander and self.commander.chief or nil) -if chief then -self:T(self.lid..string.format("Adding group %s to agents of CHIEF",group:GetName())) -chief.detectionset:AddGroup(asset.flightgroup.group) -end -elseif string.find(assignment,"Transport-")then -local uid=UTILS.Split(assignment,"-")[2] -local transport=self:GetTransportByID(uid) -if transport then -flightgroup:AddOpsTransport(transport) -end -end -end -end -function LEGION:onafterAssetDead(From,Event,To,asset,request) -self:GetParent(self,LEGION).onafterAssetDead(self,From,Event,To,asset,request) -if self.commander and self.commander.chief then -self.commander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) -end -end -function LEGION:onafterDestroyed(From,Event,To) -self:T(self.lid.."Legion warehouse destroyed!") -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -mission:Cancel() -end -for _,_cohort in pairs(self.cohorts)do -local cohort=_cohort -cohort:Stop() -end -self:GetParent(self,LEGION).onafterDestroyed(self,From,Event,To) -end -function LEGION:onafterRequest(From,Event,To,Request) -if Request.toself then -local assets=Request.cargoassets -local Mission=self:GetMissionByID(Request.assignment) -if Mission and assets then -for _,_asset in pairs(assets)do -local asset=_asset -end -end -end -self:GetParent(self,LEGION).onafterRequest(self,From,Event,To,Request) -end -function LEGION:onafterSelfRequest(From,Event,To,groupset,request) -self:GetParent(self,LEGION).onafterSelfRequest(self,From,Event,To,groupset,request) -local mission=self:GetMissionByID(request.assignment) -for _,_asset in pairs(request.assets)do -local asset=_asset -end -for _,_group in pairs(groupset:GetSet())do -local group=_group -end -end -function LEGION:onafterRequestSpawned(From,Event,To,Request,CargoGroupSet,TransportGroupSet) -self:GetParent(self,LEGION).onafterRequestSpawned(self,From,Event,To,Request,CargoGroupSet,TransportGroupSet) -end -function LEGION:onafterCaptured(From,Event,To,Coalition,Country) -self:GetParent(self,LEGION).onafterCaptured(self,From,Event,To,Coalition,Country) -if self.chief then -self.chief.commander:LegionLost(self,Coalition,Country) -self.chief:LegionLost(self,Coalition,Country) -self.chief:RemoveLegion(self) -elseif self.commander then -self.commander:LegionLost(self,Coalition,Country) -self.commander:RemoveLegion(self) -end -end -function LEGION:_CreateFlightGroup(asset) -local opsgroup=nil -if self:IsAirwing()then -opsgroup=FLIGHTGROUP:New(asset.spawngroupname) -elseif self:IsBrigade()then -opsgroup=ARMYGROUP:New(asset.spawngroupname) -elseif self:IsFleet()then -opsgroup=NAVYGROUP:New(asset.spawngroupname) -else -self:E(self.lid.."ERROR: not airwing or brigade!") -end -opsgroup:_SetLegion(self) -opsgroup.cohort=self:_GetCohortOfAsset(asset) -opsgroup.homebase=self.airbase -opsgroup.homezone=self.spawnzone -if opsgroup.cohort.weaponData then -local text="Weapon data for group:" -opsgroup.weaponData=opsgroup.weaponData or{} -for bittype,_weapondata in pairs(opsgroup.cohort.weaponData)do -local weapondata=_weapondata -opsgroup.weaponData[bittype]=UTILS.DeepCopy(weapondata) -text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km",bittype,weapondata.RangeMin/1000,weapondata.RangeMax/1000) -end -self:T3(self.lid..text) -end -return opsgroup -end -function LEGION:IsAssetOnMission(asset,MissionTypes) -if MissionTypes then -if type(MissionTypes)~="table"then -MissionTypes={MissionTypes} -end -else -MissionTypes=AUFTRAG.Type -end -if asset.flightgroup and asset.flightgroup:IsAlive()then -for _,_mission in pairs(asset.flightgroup.missionqueue or{})do -local mission=_mission -if mission:IsNotOver()then -local status=mission:GetGroupStatus(asset.flightgroup) -if(status==AUFTRAG.GroupStatus.STARTED or status==AUFTRAG.GroupStatus.EXECUTING)and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then -return true -end -end -end -end -return false -end -function LEGION:GetAssetCurrentMission(asset) -if asset.flightgroup then -return asset.flightgroup:GetMissionCurrent() -end -return nil -end -function LEGION:CountPayloadsInStock(MissionTypes,UnitTypes,Payloads) -if MissionTypes then -if type(MissionTypes)=="string"then -MissionTypes={MissionTypes} -end -end -if UnitTypes then -if type(UnitTypes)=="string"then -UnitTypes={UnitTypes} -end -end -local function _checkUnitTypes(payload) -if UnitTypes then -for _,unittype in pairs(UnitTypes)do -if unittype==payload.aircrafttype then -return true -end -end -else -return true -end -return false -end -local function _checkPayloads(payload) -if Payloads then -for _,Payload in pairs(Payloads)do -if Payload.uid==payload.uid then -return true -end -end -else -return nil -end -return false -end -local n=0 -for _,_payload in pairs(self.payloads or{})do -local payload=_payload -for _,MissionType in pairs(MissionTypes)do -local specialpayload=_checkPayloads(payload) -local compatible=AUFTRAG.CheckMissionCapability(MissionType,payload.capabilities) -local goforit=specialpayload or(specialpayload==nil and compatible) -if goforit and _checkUnitTypes(payload)then -if payload.unlimited then -return 999 -else -n=n+payload.navail -end -end -end -end -return n -end -function LEGION:CountMissionsInQueue(MissionTypes) -MissionTypes=MissionTypes or AUFTRAG.Type -local N=0 -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if mission:IsNotOver()and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then -N=N+1 -end -end -return N -end -function LEGION:CountAssets(InStock,MissionTypes,Attributes) -local N=0 -for _,_cohort in pairs(self.cohorts)do -local cohort=_cohort -N=N+cohort:CountAssets(InStock,MissionTypes,Attributes) -end -return N -end -function LEGION:GetOpsGroups(MissionTypes,Attributes) -local setLegion=SET_OPSGROUP:New() -for _,_cohort in pairs(self.cohorts)do -local cohort=_cohort -local setCohort=cohort:GetOpsGroups(MissionTypes,Attributes) -self:T2(self.lid..string.format("Found %d opsgroups of cohort %s",setCohort:Count(),cohort.name)) -setLegion:AddSet(setCohort) -end -return setLegion -end -function LEGION:CountAssetsWithPayloadsInStock(Payloads,MissionTypes,Attributes) -local N=0 -local Npayloads={} -for _,_cohort in pairs(self.cohorts)do -local cohort=_cohort -if Npayloads[cohort.aircrafttype]==nil then -Npayloads[cohort.aircrafttype]=self:CountPayloadsInStock(MissionTypes,cohort.aircrafttype,Payloads) -self:T3(self.lid..string.format("Got Npayloads=%d for type=%s",Npayloads[cohort.aircrafttype],cohort.aircrafttype)) -end -end -for _,_cohort in pairs(self.cohorts)do -local cohort=_cohort -local n=cohort:CountAssets(true,MissionTypes,Attributes) -local p=Npayloads[cohort.aircrafttype]or 0 -local m=math.min(n,p) -N=N+m -Npayloads[cohort.aircrafttype]=Npayloads[cohort.aircrafttype]-m -end -return N -end -function LEGION:CountAssetsOnMission(MissionTypes,Cohort) -local Nq=0 -local Np=0 -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if AUFTRAG.CheckMissionType(mission.type,MissionTypes or AUFTRAG.Type)then -for _,_asset in pairs(mission.assets or{})do -local asset=_asset -if asset.wid==self.uid then -if Cohort==nil or Cohort.name==asset.squadname then -local request,isqueued=self:GetRequestByID(mission.requestID[self.alias]) -if isqueued then -Nq=Nq+1 -else -Np=Np+1 -end -end -end -end -end -end -return Np+Nq,Np,Nq -end -function LEGION:GetAssetsOnMission(MissionTypes) -local assets={} -local Np=0 -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if AUFTRAG.CheckMissionType(mission.type,MissionTypes)then -for _,_asset in pairs(mission.assets or{})do -local asset=_asset -if asset.wid==self.uid then -table.insert(assets,asset) -end -end -end -end -return assets -end -function LEGION:GetAircraftTypes(onlyactive,cohorts) -local unittypes={} -for _,_cohort in pairs(cohorts or self.cohorts)do -local cohort=_cohort -if(not onlyactive)or cohort:IsOnDuty()then -local gotit=false -for _,unittype in pairs(unittypes)do -if cohort.aircrafttype==unittype then -gotit=true -break -end -end -if not gotit then -table.insert(unittypes,cohort.aircrafttype) -end -end -end -return unittypes -end -function LEGION:_CountPayloads(MissionType,Cohorts,Payloads) -local Npayloads={} -for _,_cohort in pairs(Cohorts)do -local cohort=_cohort -if Npayloads[cohort.aircrafttype]==nil then -Npayloads[cohort.aircrafttype]=cohort.legion:IsAirwing()and self:CountPayloadsInStock(MissionType,cohort.aircrafttype,Payloads)or 999 -self:T2(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s",Npayloads[cohort.aircrafttype],MissionType,cohort.aircrafttype)) -end -end -return Npayloads -end -function LEGION:RecruitAssetsForMission(Mission) -local NreqMin,NreqMax=Mission:GetRequiredAssets() -local TargetVec2=Mission:GetTargetVec2() -local Payloads=Mission.payloads -local MaxWeight=nil -if Mission.NcarriersMin then -local legions={self} -local cohorts=self.cohorts -if Mission.transportLegions or Mission.transportCohorts then -legions=Mission.transportLegions -cohorts=Mission.transportCohorts -end -local Cohorts=LEGION._GetCohorts(legions,cohorts) -local transportcohorts={} -for _,_cohort in pairs(Cohorts)do -local cohort=_cohort -local can=LEGION._CohortCan(cohort,AUFTRAG.Type.OPSTRANSPORT,Mission.carrierCategories,Mission.carrierAttributes,Mission.carrierProperties,nil,TargetVec2) -if can and(MaxWeight==nil or cohort.cargobayLimit>MaxWeight)then -MaxWeight=cohort.cargobayLimit -end -end -self:T(self.lid..string.format("Largest cargo bay available=%.1f",MaxWeight or 0)) -end -local legions={self} -local cohorts=self.cohorts -if Mission.specialLegions or Mission.specialCohorts then -legions=Mission.specialLegions -cohorts=Mission.specialCohorts -end -local Cohorts=LEGION._GetCohorts(legions,cohorts,Operation,OpsQueue) -local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,Mission.type,Mission.alert5MissionType,NreqMin,NreqMax,TargetVec2,Payloads, -Mission.engageRange,Mission.refuelSystem,nil,nil,MaxWeight,nil,Mission.attributes,Mission.properties,{Mission.engageWeaponType}) -return recruited,assets,legions -end -function LEGION:RecruitAssetsForTransport(Transport) -local cargoOpsGroups=Transport:GetCargoOpsGroups(false) -local weightGroup=0 -local TotalWeight=nil -if#cargoOpsGroups>0 then -TotalWeight=0 -for _,_opsgroup in pairs(cargoOpsGroups)do -local opsgroup=_opsgroup -local weight=opsgroup:GetWeightTotal() -if weight>weightGroup then -weightGroup=weight -end -TotalWeight=TotalWeight+weight -end -else -return false -end -local TargetVec2=Transport:GetDeployZone():GetVec2() -local NreqMin,NreqMax=Transport:GetRequiredCarriers() -local recruited,assets,legions=LEGION.RecruitCohortAssets(self.cohorts,AUFTRAG.Type.OPSTRANSPORT,nil,NreqMin,NreqMax,TargetVec2,nil,nil,nil,weightGroup,TotalWeight) -return recruited,assets,legions -end -function LEGION:RecruitAssetsForEscort(Mission,Assets) -if Mission.NescortMin and Mission.NescortMax and(Mission.NescortMin>0 or Mission.NescortMax>0)then -self:T(self.lid..string.format("Requested escort for mission %s [%s]. Required assets=%d-%d",Mission:GetName(),Mission:GetType(),Mission.NescortMin,Mission.NescortMax)) -local Cohorts={} -for _,_legion in pairs(Mission.escortLegions or{})do -local legion=_legion -for _,_cohort in pairs(legion.cohorts)do -local cohort=_cohort -table.insert(Cohorts,cohort) -end -end -for _,_cohort in pairs(Mission.escortCohorts or{})do -local cohort=_cohort -table.insert(Cohorts,cohort) -end -if#Cohorts==0 then -Cohorts=self.cohorts -end -local assigned=LEGION.AssignAssetsForEscort(self,Cohorts,Assets,Mission.NescortMin,Mission.NescortMax,Mission.escortMissionType,Mission.escortTargetTypes) -return assigned -end -return true -end -function LEGION._GetCohorts(Legions,Cohorts,Operation,OpsQueue) -OpsQueue=OpsQueue or{} -local function CheckOperation(LegionOrCohort) -if#OpsQueue==0 then -return true -end -local isAvail=true -if Operation then -isAvail=false -end -for _,_operation in pairs(OpsQueue)do -local operation=_operation -local isOps=operation:IsAssignedCohortOrLegion(LegionOrCohort) -if isOps and operation:IsRunning()then -isAvail=false -if Operation==nil then -return false -else -if Operation.uid==operation.uid then -return true -end -end -end -end -return isAvail -end -local cohorts={} -if(Legions and#Legions>0)or(Cohorts and#Cohorts>0)then -for _,_legion in pairs(Legions or{})do -local legion=_legion -local Runway=legion:IsAirwing()and legion:IsRunwayOperational()or true -if legion:IsRunning()and Runway then -for _,_cohort in pairs(legion.cohorts)do -local cohort=_cohort -if(CheckOperation(cohort.legion)or CheckOperation(cohort))and not UTILS.IsInTable(cohorts,cohort,"name")then -table.insert(cohorts,cohort) -end -end -end -end -for _,_cohort in pairs(Cohorts or{})do -local cohort=_cohort -if CheckOperation(cohort)and not UTILS.IsInTable(cohorts,cohort,"name")then -table.insert(cohorts,cohort) -end -end -end -return cohorts -end -function LEGION._CohortCan(Cohort,MissionType,Categories,Attributes,Properties,WeaponTypes,TargetVec2,RangeMax,RefuelSystem,CargoWeight,MaxWeight) -local function CheckCategory(_cohort) -local cohort=_cohort -if Categories and#Categories>0 then -for _,category in pairs(Categories)do -if category==cohort.category then -return true -end -end -else -return true -end -end -local function CheckAttribute(_cohort) -local cohort=_cohort -if Attributes and#Attributes>0 then -for _,attribute in pairs(Attributes)do -if attribute==cohort.attribute then -return true -end -end -else -return true -end -end -local function CheckProperty(_cohort) -local cohort=_cohort -if Properties and#Properties>0 then -for _,Property in pairs(Properties)do -for property,value in pairs(cohort.properties)do -if Property==property then -return true -end -end -end -else -return true -end -end -local function CheckWeapon(_cohort) -local cohort=_cohort -if WeaponTypes and#WeaponTypes>0 then -for _,WeaponType in pairs(WeaponTypes)do -if WeaponType==ENUMS.WeaponFlag.Auto then -return true -else -for _,_weaponData in pairs(cohort.weaponData or{})do -local weaponData=_weaponData -if weaponData.BitType==WeaponType then -return true -end -end -end -end -return false -else -return true -end -end -local function CheckRange(_cohort) -local cohort=_cohort -local TargetDistance=TargetVec2 and UTILS.VecDist2D(TargetVec2,cohort.legion:GetVec2())or 0 -local Rmax=cohort:GetMissionRange(WeaponTypes) -local RangeMax=RangeMax or 0 -local InRange=(RangeMax and math.max(RangeMax,Rmax)or Rmax)>=TargetDistance -return InRange -end -local function CheckRefueling(_cohort) -local cohort=_cohort -if RefuelSystem then -if cohort.tankerSystem then -return RefuelSystem==cohort.tankerSystem -else -return false -end -else -return true -end -end -local function CheckCargoWeight(_cohort) -local cohort=_cohort -if CargoWeight~=nil then -return cohort.cargobayLimit>=CargoWeight -else -return true -end -end -local function CheckMaxWeight(_cohort) -local cohort=_cohort -if MaxWeight~=nil then -cohort:T(string.format("Cohort weight=%.1f | max weight=%.1f",cohort.weightAsset,MaxWeight)) -return cohort.weightAsset<=MaxWeight -else -return true -end -end -local can=AUFTRAG.CheckMissionCapability(MissionType,Cohort.missiontypes) -if can then -can=CheckCategory(Cohort) -else -Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of mission types",Cohort.name)) -return false -end -if can then -if MissionType==AUFTRAG.Type.RELOCATECOHORT then -can=Cohort:IsRelocating() -else -can=Cohort:IsOnDuty() -end -else -Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of category",Cohort.name)) -return false -end -if can then -can=CheckAttribute(Cohort) -else -Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of readyiness",Cohort.name)) -return false -end -if can then -can=CheckProperty(Cohort) -else -Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of attribute",Cohort.name)) -return false -end -if can then -can=CheckWeapon(Cohort) -else -Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of property",Cohort.name)) -return false -end -if can then -can=CheckRange(Cohort) -else -Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of weapon type",Cohort.name)) -return false -end -if can then -can=CheckRefueling(Cohort) -else -Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of range",Cohort.name)) -return false -end -if can then -can=CheckCargoWeight(Cohort) -else -Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of refueling system",Cohort.name)) -return false -end -if can then -can=CheckMaxWeight(Cohort) -else -Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of cargo weight",Cohort.name)) -return false -end -if can then -return true -else -Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of max weight",Cohort.name)) -return false -end -return nil -end -function LEGION.RecruitCohortAssets(Cohorts,MissionTypeRecruit,MissionTypeOpt,NreqMin,NreqMax,TargetVec2,Payloads,RangeMax,RefuelSystem,CargoWeight,TotalWeight,MaxWeight,Categories,Attributes,Properties,WeaponTypes) -local Assets={} -local Legions={} -if MissionTypeOpt==nil then -MissionTypeOpt=MissionTypeRecruit -end -for _,_cohort in pairs(Cohorts)do -local cohort=_cohort -local can=LEGION._CohortCan(cohort,MissionTypeRecruit,Categories,Attributes,Properties,WeaponTypes,TargetVec2,RangeMax,RefuelSystem,CargoWeight,MaxWeight) -if can then -local assets,npayloads=cohort:RecruitAssets(MissionTypeRecruit,999) -for _,asset in pairs(assets)do -table.insert(Assets,asset) -end -end -end -LEGION._OptimizeAssetSelection(Assets,MissionTypeOpt,TargetVec2,false,TotalWeight) -for _,_asset in pairs(Assets)do -local asset=_asset -if asset.legion:IsAirwing()and not asset.payload then -asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype,MissionTypeOpt,Payloads) -end -end -for i=#Assets,1,-1 do -local asset=Assets[i] -if asset.legion:IsAirwing()and not asset.payload then -table.remove(Assets,i) -end -end -LEGION._OptimizeAssetSelection(Assets,MissionTypeOpt,TargetVec2,true,TotalWeight) -local Nassets=math.min(#Assets,NreqMax) -if#Assets>=NreqMin then -local cargobay=0 -for i=1,Nassets do -local asset=Assets[i] -asset.isReserved=true -Legions[asset.legion.alias]=asset.legion -if TotalWeight then -local N=math.floor(asset.cargobaytot/asset.nunits/CargoWeight)*asset.nunits -cargobay=cargobay+N*CargoWeight -if cargobay>=TotalWeight then -Nassets=i -break -end -end -end -for i=#Assets,Nassets+1,-1 do -local asset=Assets[i] -if asset.legion:IsAirwing()and not asset.spawned then -asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s",asset.spawngroupname)) -asset.legion:ReturnPayloadFromAsset(asset) -end -table.remove(Assets,i) -end -return true,Assets,Legions -else -for i=1,#Assets do -local asset=Assets[i] -if asset.legion:IsAirwing()and not asset.spawned then -asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s",asset.spawngroupname)) -asset.legion:ReturnPayloadFromAsset(asset) -end -end -return false,{},{} -end -return false,{},{} -end -function LEGION.UnRecruitAssets(Assets,Mission) -for i=1,#Assets do -local asset=Assets[i] -asset.isReserved=false -if asset.legion:IsAirwing()and not asset.spawned then -asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s",asset.spawngroupname)) -asset.legion:ReturnPayloadFromAsset(asset) -end -if Mission then -Mission:DelAsset(asset) -end -end -end -function LEGION:AssignAssetsForEscort(Cohorts,Assets,NescortMin,NescortMax,MissionType,TargetTypes,EngageRange) -if NescortMin and NescortMax and(NescortMin>0 or NescortMax>0)then -self:T(self.lid..string.format("Requested escort for %d assets from %d cohorts. Required escort assets=%d-%d",#Assets,#Cohorts,NescortMin,NescortMax)) -local Escorts={} -local EscortAvail=true -for _,_asset in pairs(Assets)do -local asset=_asset -local TargetVec2=asset.legion:GetVec2() -local Categories={Group.Category.HELICOPTER} -local targetTypes={"Ground Units"} -if asset.category==Group.Category.AIRPLANE then -Categories={Group.Category.AIRPLANE} -targetTypes={"Air"} -end -TargetTypes=TargetTypes or targetTypes -local Erecruited,eassets,elegions=LEGION.RecruitCohortAssets(Cohorts,AUFTRAG.Type.ESCORT,MissionType,NescortMin,NescortMax,TargetVec2,nil,nil,nil,nil,nil,nil,Categories) -if Erecruited then -Escorts[asset.spawngroupname]={EscortLegions=elegions,EscortAssets=eassets,ecategory=asset.category} -else -EscortAvail=false -break -end -end -if EscortAvail then -local N=0 -for groupname,value in pairs(Escorts)do -local Elegions=value.EscortLegions -local Eassets=value.EscortAssets -local ecategory=value.ecategory -for _,_legion in pairs(Elegions)do -local legion=_legion -local OffsetVector=nil -if ecategory==Group.Category.GROUND then -OffsetVector={} -OffsetVector.x=0 -OffsetVector.y=UTILS.FeetToMeters(1000) -OffsetVector.z=0 -elseif MissionType==AUFTRAG.Type.SEAD then -OffsetVector={} -OffsetVector.x=-100 -OffsetVector.y=500 -OffsetVector.z=500 -end -local escort=AUFTRAG:NewESCORT(groupname,OffsetVector,EngageRange,TargetTypes) -if MissionType==AUFTRAG.Type.SEAD then -escort.missionTask=ENUMS.MissionTask.SEAD -local DCStask=CONTROLLABLE.EnRouteTaskSEAD(nil) -table.insert(escort.enrouteTasks,DCStask) -end -for _,_asset in pairs(Eassets)do -local asset=_asset -escort:AddAsset(asset) -N=N+1 -end -self:MissionAssign(escort,{legion}) -end -end -self:T(self.lid..string.format("Recruited %d escort assets",N)) -return true -else -self:T(self.lid..string.format("Could not get at least one escort!")) -for groupname,value in pairs(Escorts)do -local Eassets=value.EscortAssets -LEGION.UnRecruitAssets(Eassets) -end -return false -end -else -self:T(self.lid..string.format("No escort required! NescortMin=%s, NescortMax=%s",tostring(NescortMin),tostring(NescortMax))) -return true -end -end -function LEGION:AssignAssetsForTransport(Legions,CargoAssets,NcarriersMin,NcarriersMax,DeployZone,DisembarkZone,Categories,Attributes,Properties) -if NcarriersMin and NcarriersMax and(NcarriersMin>0 or NcarriersMax>0)then -local Cohorts=LEGION._GetCohorts(Legions) -local CargoLegions={};local CargoWeight=nil;local TotalWeight=0 -for _,_asset in pairs(CargoAssets)do -local asset=_asset -CargoLegions[asset.legion.alias]=asset.legion -if CargoWeight==nil or asset.weight>CargoWeight then -CargoWeight=asset.weight -end -TotalWeight=TotalWeight+asset.weight -end -self:T(self.lid..string.format("Cargo weight=%.1f",CargoWeight)) -self:T(self.lid..string.format("Total weight=%.1f",TotalWeight)) -local TargetVec2=DeployZone:GetVec2() -local TransportAvail,CarrierAssets,CarrierLegions= -LEGION.RecruitCohortAssets(Cohorts,AUFTRAG.Type.OPSTRANSPORT,nil,NcarriersMin,NcarriersMax,TargetVec2,nil,nil,nil,CargoWeight,TotalWeight,nil,Categories,Attributes,Properties) -if TransportAvail then -local Transport=OPSTRANSPORT:New(nil,nil,DeployZone) -if DisembarkZone then -Transport:SetDisembarkZone(DisembarkZone) -end -self:T(self.lid..string.format("Transport available with %d carrier assets",#CarrierAssets)) -for _,_legion in pairs(CargoLegions)do -local legion=_legion -local pickupzone=legion.spawnzone -local tpz=Transport:AddTransportZoneCombo(nil,pickupzone,Transport:GetDeployZone()) -tpz.PickupAirbase=legion:IsRunwayOperational()and legion.airbase or nil -Transport:SetEmbarkZone(legion.spawnzone,tpz) -for _,_asset in pairs(CargoAssets)do -local asset=_asset -if asset.legion.alias==legion.alias then -Transport:AddAssetCargo(asset,tpz) -end -end -end -for _,_asset in pairs(CarrierAssets)do -local asset=_asset -Transport:AddAsset(asset) -end -self:TransportAssign(Transport,CarrierLegions) -return true,Transport -else -self:T(self.lid..string.format("Transport assets could not be allocated ==> Unrecruiting assets")) -LEGION.UnRecruitAssets(CarrierAssets) -return false,nil -end -return nil,nil -end -return true,nil -end -function LEGION.CalculateAssetMissionScore(asset,MissionType,TargetVec2,IncludePayload,TotalWeight) -local score=0 -if asset.skill==AI.Skill.AVERAGE then -score=score+0 -elseif asset.skill==AI.Skill.GOOD then -score=score+10 -elseif asset.skill==AI.Skill.HIGH then -score=score+20 -elseif asset.skill==AI.Skill.EXCELLENT then -score=score+30 -end -score=score+asset.cohort:GetMissionPeformance(MissionType) -local function scorePayload(Payload,MissionType) -for _,Capability in pairs(Payload.capabilities)do -local capability=Capability -if capability.MissionType==MissionType then -return capability.Performance -end -end -return 0 -end -if IncludePayload and asset.payload then -score=score+scorePayload(asset.payload,MissionType) -end -local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2()or asset.legion:GetVec2() -local distance=0 -if TargetVec2 and OrigVec2 then -distance=UTILS.MetersToNM(UTILS.VecDist2D(OrigVec2,TargetVec2)) -if asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then -distance=UTILS.Round(distance/10,0) -else -distance=UTILS.Round(distance,0) -end -end -score=score-distance -if asset.spawned and asset.flightgroup and asset.flightgroup:IsAlive()then -local currmission=asset.flightgroup:GetMissionCurrent() -if currmission then -if currmission.type==AUFTRAG.Type.ALERT5 and currmission.alert5MissionType==MissionType then -score=score+25 -elseif(currmission.type==AUFTRAG.Type.GCICAP or currmission.type==AUFTRAG.Type.PATROLRACETRACK)and MissionType==AUFTRAG.Type.INTERCEPT then -score=score+35 -elseif(currmission.type==AUFTRAG.Type.ONGUARD or currmission.type==AUFTRAG.Type.PATROLZONE)and(MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK)then -score=score+25 -elseif currmission.type==AUFTRAG.Type.NOTHING then -score=score+25 -end -end -if MissionType==AUFTRAG.Type.OPSTRANSPORT or MissionType==AUFTRAG.Type.AMMOSUPPLY or MissionType==AUFTRAG.Type.AWACS or MissionType==AUFTRAG.Type.FUELSUPPLY or MissionType==AUFTRAG.Type.TANKER then -score=score-10 -else -if asset.flightgroup:IsOutOfAmmo()then -score=score-1000 -end -end -end -if MissionType==AUFTRAG.Type.OPSTRANSPORT then -if TotalWeight then -if asset.cargobaymax=2 then -asset.legion:I(asset.legion.lid..string.format("Asset %s [spawned=%s] score=%d",asset.spawngroupname,tostring(asset.spawned),score)) -end -return score -end -function LEGION._OptimizeAssetSelection(assets,MissionType,TargetVec2,IncludePayload,TotalWeight) -for _,_asset in pairs(assets)do -local asset=_asset -asset.score=LEGION.CalculateAssetMissionScore(asset,MissionType,TargetVec2,IncludePayload,TotalWeight) -if IncludePayload then -local RandomScoreMax=asset.legion and asset.legion.RandomAssetScore or LEGION.RandomAssetScore -local RandomScore=math.random(0,RandomScoreMax) -asset.score=asset.score+RandomScore -end -end -local function optimize(a,b) -local assetA=a -local assetB=b -return(assetA.score>assetB.score) -end -table.sort(assets,optimize) -if LEGION.verbose>0 then -local text=string.format("Optimized %d assets for %s mission/transport (payload=%s):",#assets,MissionType,tostring(IncludePayload)) -for i,Asset in pairs(assets)do -local asset=Asset -text=text..string.format("\n%d. %s [%s]: score=%d",i,asset.spawngroupname,asset.squadname,asset.score or-1) -asset.score=nil -end -env.info(text) -end -end -function LEGION:GetMissionByID(mid) -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if mission.auftragsnummer==tonumber(mid)then -return mission -end -end -return nil -end -function LEGION:GetTransportByID(uid) -for _,_transport in pairs(self.transportqueue)do -local transport=_transport -if transport.uid==tonumber(uid)then -return transport -end -end -return nil -end -function LEGION:GetMissionFromRequestID(RequestID) -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -local mid=mission.requestID[self.alias] -if mid and mid==RequestID then -return mission -end -end -return nil -end -function LEGION:GetMissionFromRequest(Request) -return self:GetMissionFromRequestID(Request.uid) -end -function LEGION:FetchPayloadFromStock(UnitType,MissionType,Payloads) -return nil -end -function LEGION:ReturnPayloadFromAsset(asset) -return nil -end -NAVYGROUP={ -ClassName="NAVYGROUP", -turning=false, -intowind=nil, -intowindcounter=0, -Qintowind={}, -pathCorridor=400, -engage={}, -} -NAVYGROUP.version="1.0.2" -function NAVYGROUP:New(group) -local og=_DATABASE:GetOpsGroup(group) -if og then -og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) -return og -end -local self=BASE:Inherit(self,OPSGROUP:New(group)) -self.lid=string.format("NAVYGROUP %s | ",self.groupname) -self:SetDefaultROE() -self:SetDefaultAlarmstate() -self:SetDefaultEPLRS(self.isEPLRS) -self:SetDefaultEmission() -self:SetDetection() -self:SetPatrolAdInfinitum(true) -self:SetPathfinding(false) -self:AddTransition("*","FullStop","Holding") -self:AddTransition("*","Cruise","Cruising") -self:AddTransition("*","RTZ","Returning") -self:AddTransition("Returning","Returned","Returned") -self:AddTransition("*","Detour","Cruising") -self:AddTransition("*","DetourReached","*") -self:AddTransition("*","Retreat","Retreating") -self:AddTransition("Retreating","Retreated","Retreated") -self:AddTransition("Cruising","EngageTarget","Engaging") -self:AddTransition("Holding","EngageTarget","Engaging") -self:AddTransition("OnDetour","EngageTarget","Engaging") -self:AddTransition("Engaging","Disengage","Cruising") -self:AddTransition("*","TurnIntoWind","Cruising") -self:AddTransition("*","TurnedIntoWind","*") -self:AddTransition("*","TurnIntoWindStop","*") -self:AddTransition("*","TurnIntoWindOver","*") -self:AddTransition("*","TurningStarted","*") -self:AddTransition("*","TurningStopped","*") -self:AddTransition("*","CollisionWarning","*") -self:AddTransition("*","ClearAhead","*") -self:AddTransition("Cruising","Dive","Cruising") -self:AddTransition("Engaging","Dive","Engaging") -self:AddTransition("Cruising","Surface","Cruising") -self:AddTransition("Engaging","Surface","Engaging") -self:_InitWaypoints() -self:_InitGroup() -self:HandleEvent(EVENTS.Birth,self.OnEventBirth) -self:HandleEvent(EVENTS.Dead,self.OnEventDead) -self:HandleEvent(EVENTS.RemoveUnit,self.OnEventRemoveUnit) -self.timerStatus=TIMER:New(self.Status,self):Start(1,30) -self.timerQueueUpdate=TIMER:New(self._QueueUpdate,self):Start(2,5) -self.timerCheckZone=TIMER:New(self._CheckInZones,self):Start(2,60) -_DATABASE:AddOpsGroup(self) -return self -end -function NAVYGROUP:SetPatrolAdInfinitum(switch) -if switch==false then -self.adinfinitum=false -else -self.adinfinitum=true -end -return self -end -function NAVYGROUP:SetPathfinding(Switch,CorridorWidth) -self.pathfindingOn=Switch -self.pathCorridor=CorridorWidth or 400 -return self -end -function NAVYGROUP:SetPathfindingOn(CorridorWidth) -self:SetPathfinding(true,CorridorWidth) -return self -end -function NAVYGROUP:SetPathfindingOff() -self:SetPathfinding(false,self.pathCorridor) -return self -end -function NAVYGROUP:AddTaskFireAtPoint(Coordinate,Clock,Radius,Nshots,WeaponType,Prio) -local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,Coordinate:GetVec2(),Radius,Nshots,WeaponType) -local task=self:AddTask(DCStask,Clock,nil,Prio) -return task -end -function NAVYGROUP:AddTaskWaypointFireAtPoint(Coordinate,Waypoint,Radius,Nshots,WeaponType,Prio,Duration) -Waypoint=Waypoint or self:GetWaypointNext() -local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,Coordinate:GetVec2(),Radius,Nshots,WeaponType) -local task=self:AddTaskWaypoint(DCStask,Waypoint,nil,Prio,Duration) -return task -end -function NAVYGROUP:AddTaskAttackGroup(TargetGroup,WeaponExpend,WeaponType,Clock,Prio) -local DCStask=CONTROLLABLE.TaskAttackGroup(nil,TargetGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit,GroupAttack) -local task=self:AddTask(DCStask,Clock,nil,Prio) -return task -end -function NAVYGROUP:_CreateTurnIntoWind(starttime,stoptime,speed,uturn,offset) -local Tnow=timer.getAbsTime() -if starttime and type(starttime)=="number"then -starttime=UTILS.SecondsToClock(Tnow+starttime) -end -starttime=starttime or UTILS.SecondsToClock(Tnow) -local Tstart=UTILS.ClockToSeconds(starttime) -if uturn==nil then -uturn=true -end -local Tstop=Tstart+90*60 -if stoptime==nil then -Tstop=Tstart+90*60 -elseif type(stoptime)=="number"then -Tstop=Tstart+stoptime -else -Tstop=UTILS.ClockToSeconds(stoptime) -end -if Tstart>Tstop then -self:E(string.format("ERROR:Into wind stop time %s lies before start time %s. Input rejected!",UTILS.SecondsToClock(Tstart),UTILS.SecondsToClock(Tstop))) -return self -end -if Tstop<=Tnow then -self:E(string.format("WARNING: Into wind stop time %s already over. Tnow=%s! Input rejected.",UTILS.SecondsToClock(Tstop),UTILS.SecondsToClock(Tnow))) -return self -end -self.intowindcounter=self.intowindcounter+1 -local recovery={} -recovery.Tstart=Tstart -recovery.Tstop=Tstop -recovery.Open=false -recovery.Over=false -recovery.Speed=speed or 20 -recovery.Uturn=uturn and uturn or false -recovery.Offset=offset or 0 -recovery.Id=self.intowindcounter -return recovery -end -function NAVYGROUP:AddTurnIntoWind(starttime,stoptime,speed,uturn,offset) -local recovery=self:_CreateTurnIntoWind(starttime,stoptime,speed,uturn,offset) -table.insert(self.Qintowind,recovery) -return recovery -end -function NAVYGROUP:RemoveTurnIntoWind(IntoWindData) -if self.intowind and self.intowind.Id==IntoWindData.Id then -self:TurnIntoWindStop() -return -end -for i,_tiw in pairs(self.Qintowind)do -local tiw=_tiw -if tiw.Id==IntoWindData.Id then -table.remove(self.Qintowind,i) -break -end -end -return self -end -function NAVYGROUP:IsHolding() -return self:Is("Holding") -end -function NAVYGROUP:IsCruising() -return self:Is("Cruising") -end -function NAVYGROUP:IsOnDetour() -return self:Is("OnDetour") -end -function NAVYGROUP:IsDiving() -return self:Is("Diving") -end -function NAVYGROUP:IsTurning() -return self.turning -end -function NAVYGROUP:IsSteamingIntoWind() -if self.intowind then -return true -else -return false -end -end -function NAVYGROUP:IsRecovering() -if self.intowind then -if self.intowind.Recovery==true then -return true -else -return false -end -else -return false -end -end -function NAVYGROUP:IsLaunching() -if self.intowind then -if self.intowind.Recovery==false then -return true -else -return false -end -else -return false -end -end -function NAVYGROUP:Status(From,Event,To) -local fsmstate=self:GetState() -local alive=self:IsAlive() -local freepath=0 -if alive then -self:_UpdatePosition() -self:_CheckDetectedUnits() -self:_CheckTurning() -local disttoWP=math.min(self:GetDistanceToWaypoint(),UTILS.NMToMeters(10)) -freepath=disttoWP -if not self:IsTurning()then -freepath=self:_CheckFreePath(freepath,100) -if disttoWP>1 and freepathself.Twaiting+self.dTwait then -self.Twaiting=nil -self.dTwait=nil -if self:_CountPausedMissions()>0 then -self:UnpauseMission() -else -self:Cruise() -end -end -end -end -local mission=self:GetMissionCurrent() -if mission and mission.updateDCSTask then -if mission.type==AUFTRAG.Type.CAPTUREZONE then -local Task=mission:GetGroupWaypointTask(self) -if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING or mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.STARTED then -self:_UpdateTask(Task,mission) -end -end -end -else -self:_CheckDamage() -end -if alive~=nil then -if self.verbose>=1 then -local nelem=self:CountElements() -local Nelem=#self.elements -local nTaskTot,nTaskSched,nTaskWP=self:CountRemainingTasks() -local nMissions=self:CountRemainingMissison() -local roe=self:GetROE()or-1 -local als=self:GetAlarmstate()or-1 -local wpidxCurr=self.currentwp -local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr)or 0 -local wpidxNext=self:GetWaypointIndexNext()or 0 -local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext)or 0 -local wpN=#self.waypoints or 0 -local wpF=tostring(self.passedfinalwp) -local speed=UTILS.MpsToKnots(self.velocity or 0) -local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) -local alt=self.position and self.position.y or 0 -local hdg=self.heading or 0 -local life=self.life or 0 -local ammo=self:GetAmmoTot().Total -local ndetected=self.detectionOn and tostring(self.detectedunits:Count())or"Off" -local cargo=0 -for _,_element in pairs(self.elements)do -local element=_element -cargo=cargo+element.weightCargo -end -local intowind=self:IsSteamingIntoWind()and UTILS.SecondsToClock(self.intowind.Tstop-timer.getAbsTime(),true)or"N/A" -local turning=tostring(self:IsTurning()) -local text=string.format("%s [%d/%d]: ROE/AS=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f | Turn=%s Collision=%d IntoWind=%s", -fsmstate,nelem,Nelem,roe,als,nTaskTot,nMissions,wpidxCurr,wpuidCurr,wpidxNext,wpuidNext,wpN,wpF,life,speed,speedEx,hdg,ammo,ndetected,cargo,turning,freepath,intowind) -self:I(self.lid..text) -end -else -local text=string.format("State %s: Alive=%s",fsmstate,tostring(self:IsAlive())) -self:T(self.lid..text) -end -if alive and self.verbose>=2 and#self.Qintowind>0 then -local text=string.format(self.lid.."Turn into wind time windows:") -if#self.Qintowind==0 then -text=text.." none!" -end -for i,_recovery in pairs(self.Qintowind)do -local recovery=_recovery -local Cstart=UTILS.SecondsToClock(recovery.Tstart) -local Cstop=UTILS.SecondsToClock(recovery.Tstop) -text=text..string.format("\n[%d] ID=%d Start=%s Stop=%s Open=%s Over=%s",i,recovery.Id,Cstart,Cstop,tostring(recovery.Open),tostring(recovery.Over)) -end -self:I(self.lid..text) -end -if self:IsCruising()and self.detectionOn and self.engagedetectedOn then -local targetgroup,targetdist=self:_GetDetectedTarget() -if targetgroup then -self:I(self.lid..string.format("Engaging target group %s at distance %d meters",targetgroup:GetName(),targetdist)) -self:EngageTarget(targetgroup) -end -end -self:_CheckCargoTransport() -self:_PrintTaskAndMissionStatus() -end -function NAVYGROUP:onafterElementSpawned(From,Event,To,Element) -self:T(self.lid..string.format("Element spawned %s",Element.name)) -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.SPAWNED) -end -function NAVYGROUP:onafterSpawned(From,Event,To) -self:T(self.lid..string.format("Group spawned!")) -if self.verbose>=1 then -local text=string.format("Initialized Navy Group %s:\n",self.groupname) -text=text..string.format("Unit type = %s\n",self.actype) -text=text..string.format("Speed max = %.1f Knots\n",UTILS.KmphToKnots(self.speedMax)) -text=text..string.format("Speed cruise = %.1f Knots\n",UTILS.KmphToKnots(self.speedCruise)) -text=text..string.format("Weight = %.1f kg\n",self:GetWeightTotal()) -text=text..string.format("Cargo bay = %.1f kg\n",self:GetFreeCargobay()) -text=text..string.format("Has EPLRS = %s\n",tostring(self.isEPLRS)) -text=text..string.format("Is Submarine = %s\n",tostring(self.isSubmarine)) -text=text..string.format("Elements = %d\n",#self.elements) -text=text..string.format("Waypoints = %d\n",#self.waypoints) -text=text..string.format("Radio = %.1f MHz %s %s\n",self.radio.Freq,UTILS.GetModulationName(self.radio.Modu),tostring(self.radio.On)) -text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Rockets,self.ammo.Missiles,self.ammo.Torpedos) -text=text..string.format("FSM state = %s\n",self:GetState()) -text=text..string.format("Is alive = %s\n",tostring(self:IsAlive())) -text=text..string.format("LateActivate = %s\n",tostring(self:IsLateActivated())) -self:I(self.lid..text) -end -self:_UpdatePosition() -self.isDead=false -self.isDestroyed=false -if self.isAI then -self:SwitchROE(self.option.ROE) -self:SwitchAlarmstate(self.option.Alarm) -self:SwitchEmission(self.option.Emission) -self:SwitchEPLRS(self.option.EPLRS) -self:SwitchInvisible(self.option.Invisible) -self:SwitchImmortal(self.option.Immortal) -self:_SwitchTACAN() -self:_SwitchICLS() -if self.radioDefault then -else -self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,false) -end -if#self.waypoints>1 then -self:__Cruise(-0.1) -else -self:FullStop() -end -end -end -function NAVYGROUP:onbeforeUpdateRoute(From,Event,To,n,Speed,Depth) -local allowed=true -local trepeat=nil -if self:IsWaiting()then -self:T(self.lid.."Update route denied. Group is WAITING!") -return false -elseif self:IsInUtero()then -self:T(self.lid.."Update route denied. Group is INUTERO!") -return false -elseif self:IsDead()then -self:T(self.lid.."Update route denied. Group is DEAD!") -return false -elseif self:IsStopped()then -self:T(self.lid.."Update route denied. Group is STOPPED!") -return false -elseif self:IsHolding()then -self:T(self.lid.."Update route denied. Group is holding position!") -return false -elseif self:IsEngaging()then -self:T(self.lid.."Update route allowed. Group is engaging!") -return true -end -if self.taskcurrent>0 then -local task=self:GetTaskByID(self.taskcurrent) -if task then -if task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then -self:T2(self.lid.."Allowing update route for Task: PatrolZone") -elseif task.dcstask.id==AUFTRAG.SpecialTask.RECON then -self:T2(self.lid.."Allowing update route for Task: ReconMission") -elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then -self:T2(self.lid.."Allowing update route for Task: Relocate Cohort") -elseif task.dcstask.id==AUFTRAG.SpecialTask.REARMING then -self:T2(self.lid.."Allowing update route for Task: Rearming") -else -local taskname=task and task.description or"No description" -self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s",self.taskcurrent,tostring(taskname))) -allowed=false -end -else -self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d (>0!) but no task?!",self.taskcurrent)) -allowed=false -end -end -if not self.isAI then -allowed=false -end -self:T2(self.lid..string.format("Onbefore Updateroute in state %s: allowed=%s (repeat in %s)",self:GetState(),tostring(allowed),tostring(trepeat))) -if trepeat then -self:__UpdateRoute(trepeat,n) -end -return allowed -end -function NAVYGROUP:onafterUpdateRoute(From,Event,To,n,N,Speed,Depth) -n=n or self:GetWaypointIndexNext() -N=N or#self.waypoints -N=math.min(N,#self.waypoints) -local waypoints={} -for i=n,N do -local wp=UTILS.DeepCopy(self.waypoints[i]) -if Speed then -wp.speed=UTILS.KnotsToMps(Speed) -else -if wp.speed<0.1 then -wp.speed=UTILS.KmphToMps(self.speedCruise) -end -end -if Depth then -wp.alt=-Depth -elseif self.depth then -wp.alt=-self.depth -else -wp.alt=wp.alt or 0 -end -if i==n then -self.speedWp=wp.speed -self.altWp=wp.alt -end -table.insert(waypoints,wp) -end -local current=self:GetCoordinate():WaypointNaval(UTILS.MpsToKmph(self.speedWp),self.altWp) -table.insert(waypoints,1,current) -if self:IsEngaging()or not self.passedfinalwp then -if self.verbose>=10 then -for i=1,#waypoints do -local wp=waypoints[i] -local text=string.format("%s Waypoint [%d] UID=%d speed=%d",self.groupname,i-1,wp.uid or-1,wp.speed) -self:I(self.lid..text) -COORDINATE:NewFromWaypoint(wp):MarkToAll(text) -end -end -self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Depth=%d m",self.currentwp,n,#waypoints,#self.waypoints,UTILS.MpsToKnots(self.speedWp),self.altWp)) -self:Route(waypoints) -else -self:E(self.lid..string.format("WARNING: Passed final WP ==> Full Stop!")) -self:FullStop() -end -end -function NAVYGROUP:onafterDetour(From,Event,To,Coordinate,Speed,Depth,ResumeRoute) -Depth=Depth or 0 -Speed=Speed or self:GetSpeedCruise() -local uid=self:GetWaypointCurrent().uid -local wp=self:AddWaypoint(Coordinate,Speed,uid,Depth,true) -if ResumeRoute then -wp.detour=1 -else -wp.detour=0 -end -end -function NAVYGROUP:onafterDetourReached(From,Event,To) -self:T(self.lid.."Group reached detour coordinate.") -end -function NAVYGROUP:onafterTurnIntoWind(From,Event,To,IntoWind) -local heading,speed=self:GetHeadingIntoWind(IntoWind.Offset,IntoWind.Speed) -IntoWind.Heading=heading -IntoWind.Open=true -IntoWind.Coordinate=self:GetCoordinate(true) -self.intowind=IntoWind -self:T(self.lid..string.format("Steaming into wind: Heading=%03d Speed=%.1f, Tstart=%d Tstop=%d",IntoWind.Heading,speed,IntoWind.Tstart,IntoWind.Tstop)) -local distance=UTILS.NMToMeters(1000) -local coord=self:GetCoordinate() -local Coord=coord:Translate(distance,IntoWind.Heading) -local uid=self:GetWaypointCurrent().uid -local wptiw=self:AddWaypoint(Coord,speed,uid) -wptiw.intowind=true -IntoWind.waypoint=wptiw -if IntoWind.Uturn and false then -IntoWind.Coordinate:MarkToAll("Return coord") -end -end -function NAVYGROUP:onbeforeTurnIntoWindStop(From,Event,To) -if self.intowind then -return true -else -return false -end -end -function NAVYGROUP:onafterTurnIntoWindStop(From,Event,To) -self:TurnIntoWindOver(self.intowind) -end -function NAVYGROUP:onafterTurnIntoWindOver(From,Event,To,IntoWindData) -if IntoWindData and self.intowind and IntoWindData.Id==self.intowind.Id then -self:T2(self.lid.."Turn Into Wind Over!") -self.intowind.Over=true -self.intowind.Open=false -self:RemoveWaypointByID(self.intowind.waypoint.uid) -if self.intowind.Uturn then -self:T(self.lid.."FF Turn Into Wind Over ==> Uturn!") -local uid=self:GetWaypointCurrent().uid -local wp=self:AddWaypoint(self.intowind.Coordinate,self:GetSpeedCruise(),uid);wp.temp=true -else -local indx=self:GetWaypointIndexNext() -local speed=self:GetSpeedToWaypoint(indx) -self:T(self.lid..string.format("FF Turn Into Wind Over ==> Next WP Index=%d at %.1f knots via update route!",indx,speed)) -self:__UpdateRoute(-1,indx,nil,speed) -end -self.intowind=nil -self:RemoveTurnIntoWind(IntoWindData) -end -end -function NAVYGROUP:onafterFullStop(From,Event,To) -self:T(self.lid.."Full stop ==> holding") -local pos=self:GetCoordinate() -local wp=pos:WaypointNaval(0) -self:Route({wp}) -end -function NAVYGROUP:onafterCruise(From,Event,To,Speed) -self.Twaiting=nil -self.dTwait=nil -self.depth=nil -self:__UpdateRoute(-0.1,nil,nil,Speed) -end -function NAVYGROUP:onafterDive(From,Event,To,Depth,Speed) -Depth=Depth or 50 -self:I(self.lid..string.format("Diving to %d meters",Depth)) -self.depth=Depth -self:__UpdateRoute(-1,nil,nil,Speed) -end -function NAVYGROUP:onafterSurface(From,Event,To,Speed) -self.depth=0 -self:__UpdateRoute(-1,nil,nil,Speed) -end -function NAVYGROUP:onafterTurningStarted(From,Event,To) -self.turning=true -end -function NAVYGROUP:onafterTurningStopped(From,Event,To) -self.turning=false -self.collisionwarning=false -if self:IsSteamingIntoWind()then -self:TurnedIntoWind() -end -end -function NAVYGROUP:onafterCollisionWarning(From,Event,To,Distance) -self:T(self.lid..string.format("Iceberg ahead in %d meters!",Distance or-1)) -self.collisionwarning=true -end -function NAVYGROUP:onafterEngageTarget(From,Event,To,Target) -self:T(self.lid.."Engaging Target") -if Target:IsInstanceOf("TARGET")then -self.engage.Target=Target -else -self.engage.Target=TARGET:New(Target) -end -self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) -local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.9) -self.engage.roe=self:GetROE() -self.engage.alarmstate=self:GetAlarmstate() -self:SwitchAlarmstate(ENUMS.AlarmState.Auto) -self:SwitchROE(ENUMS.ROE.OpenFire) -local uid=self:GetWaypointCurrent().uid -self.engage.Waypoint=self:AddWaypoint(intercoord,nil,uid,Formation,true) -self.engage.Waypoint.detour=1 -end -function NAVYGROUP:_UpdateEngageTarget() -if self.engage.Target and self.engage.Target:IsAlive()then -local vec3=self.engage.Target:GetVec3() -if vec3 then -local dist=UTILS.VecDist3D(vec3,self.engage.Coordinate:GetVec3()) -if dist>100 then -self.engage.Coordinate:UpdateFromVec3(vec3) -local uid=self:GetWaypointCurrent().uid -self:RemoveWaypointByID(self.engage.Waypoint.uid) -local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.9) -self.engage.Waypoint=self:AddWaypoint(intercoord,nil,uid,Formation,true) -self.engage.Waypoint.detour=0 -end -else -self:Disengage() -end -else -self:Disengage() -end -end -function NAVYGROUP:onafterDisengage(From,Event,To) -self:T(self.lid.."Disengage Target") -self:SwitchROE(self.engage.roe) -self:SwitchAlarmstate(self.engage.alarmstate) -local task=self:GetTaskCurrent() -if task and task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK then -self:T(self.lid.."Disengage with current task GROUNDATTACK ==> Task Done!") -self:TaskDone(task) -end -if self.engage.Waypoint then -self:RemoveWaypointByID(self.engage.Waypoint.uid) -end -self:_CheckGroupDone(1) -end -function NAVYGROUP:onafterOutOfAmmo(From,Event,To) -self:T(self.lid..string.format("Group is out of ammo at t=%.3f",timer.getTime())) -if self.retreatOnOutOfAmmo then -self:__Retreat(-1) -return -end -if self.rtzOnOutOfAmmo then -self:__RTZ(-1) -end -local task=self:GetTaskCurrent() -if task then -if task.dcstask.id=="FireAtPoint"or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then -self:T(self.lid..string.format("Cancelling current %s task because out of ammo!",task.dcstask.id)) -self:TaskCancel(task) -end -end -end -function NAVYGROUP:onafterRTZ(From,Event,To,Zone,Formation) -local zone=Zone or self.homezone -self:CancelAllMissions() -if zone then -if self:IsInZone(zone)then -self:Returned() -else -self:T(self.lid..string.format("RTZ to Zone %s",zone:GetName())) -local Coordinate=zone:GetRandomCoordinate() -local uid=self:GetWaypointCurrentUID() -local wp=self:AddWaypoint(Coordinate,nil,uid,Formation,true) -wp.detour=0 -end -else -self:T(self.lid.."ERROR: No RTZ zone given!") -end -end -function NAVYGROUP:onafterReturned(From,Event,To) -self:T(self.lid..string.format("Group returned")) -if self.legion then -self:T(self.lid..string.format("Adding group back to warehouse stock")) -self.legion:__AddAsset(10,self.group,1) -end -end -function NAVYGROUP:AddWaypoint(Coordinate,Speed,AfterWaypointWithID,Depth,Updateroute) -local coordinate=self:_CoordinateFromObject(Coordinate) -local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) -Speed=Speed or self:GetSpeedCruise() -local wp=coordinate:WaypointNaval(UTILS.KnotsToKmph(Speed),Depth) -local waypoint=self:_CreateWaypoint(wp) -if Depth then -waypoint.alt=UTILS.FeetToMeters(Depth) -end -self:_AddWaypoint(waypoint,wpnumber) -self:T(self.lid..string.format("Adding NAVAL waypoint index=%d uid=%d, speed=%.1f knots. Last waypoint passed was #%d. Total waypoints #%d",wpnumber,waypoint.uid,Speed,self.currentwp,#self.waypoints)) -if Updateroute==nil or Updateroute==true then -self:__UpdateRoute(-0.01) -end -return waypoint -end -function NAVYGROUP:_InitGroup(Template) -if self.groupinitialized then -self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") -return -end -local template=Template or self:_GetTemplate() -self.isAI=true -self.isLateActivated=template.lateActivation -self.isUncontrolled=false -self.speedMax=self.group:GetSpeedMax() -if self.speedMax>3.6 then -self.isMobile=true -else -self.isMobile=false -end -self.speedCruise=self.speedMax*0.7 -self.ammo=self:GetAmmoTot() -self.radio.On=true -self.radio.Freq=tonumber(template.units[1].frequency)/1000000 -self.radio.Modu=tonumber(template.units[1].modulation) -self.optionDefault.Formation="Off Road" -self.option.Formation=self.optionDefault.Formation -self:SetDefaultTACAN(nil,nil,nil,nil,true) -self.tacan=UTILS.DeepCopy(self.tacanDefault) -self:SetDefaultICLS(nil,nil,nil,true) -self.icls=UTILS.DeepCopy(self.iclsDefault) -local units=self.group:GetUnits() -local dcsgroup=Group.getByName(self.groupname) -local size0=dcsgroup:getInitialSize() -if#units~=size0 then -self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!",#units,size0)) -end -for _,unit in pairs(units)do -self:_AddElementByName(unit:GetName()) -end -self.groupinitialized=true -return self -end -function NAVYGROUP:_CheckFreePath(DistanceMax,dx) -local distance=DistanceMax or 5000 -local dx=dx or 100 -if self:IsTurning()then -return distance -end -local offsetY=0.1 -if UTILS.GetDCSMap()==DCSMAP.Caucasus then -offsetY=5.01 -end -local vec3=self:GetVec3() -vec3.y=offsetY -local heading=self:GetHeading() -local function LoS(dist) -local checkvec3=UTILS.VecTranslate(vec3,dist,heading) -local los=land.isVisible(vec3,checkvec3) -return los -end -if LoS(DistanceMax)then -return DistanceMax -end -local function check() -local xmin=0 -local xmax=DistanceMax -local Nmax=100 -local eps=100 -local N=1 -while N<=Nmax do -local d=xmax-xmin -local x=xmin+d/2 -local los=LoS(x) -self:T(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s",N,xmin,xmax,x,d,tostring(los))) -if los and d<=eps then -return x -end -if los then -xmin=x -else -xmax=x -end -N=N+1 -end -return 0 -end -local _check=check() -return _check -end -function NAVYGROUP:_CheckTurning() -local unit=self.group:GetUnit(1) -if unit and unit:IsAlive()then -local vNew=self.orientX -local vLast=self.orientXLast -vNew.y=0;vLast.y=0 -local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast))) -local turning=math.abs(deltaLast)>=2 -if self.turning and not turning then -self:TurningStopped() -elseif turning and not self.turning then -self:TurningStarted() -end -self.turning=turning -end -end -function NAVYGROUP:_CheckTurnsIntoWind() -local time=timer.getAbsTime() -if self.intowind then -if time>=self.intowind.Tstop then -self:TurnIntoWindOver(self.intowind) -end -else -local IntoWind=self:GetTurnIntoWindNext() -if IntoWind then -self:TurnIntoWind(IntoWind) -end -end -end -function NAVYGROUP:GetTurnIntoWindNext() -if#self.Qintowind>0 then -local time=timer.getAbsTime() -table.sort(self.Qintowind,function(a,b)return a.Tstart=recovery.Tstart and timevdeckMax then -v=Vmax -theta=math.asin(v/(vwind*C))-math.asin(-1/C) -elseif vdeckvwind then -theta=math.pi/2 -v=math.sqrt(vdeck^2-vwind^2) -else -theta=math.asin(vdeck*math.sin(alpha)/vwind) -v=vdeck*math.cos(alpha)-vwind*math.cos(theta) -end -local intowind=(540+(windto+math.deg(theta)))%360 -self:T(self.lid..string.format("Heading into Wind: vship=%.1f, vwind=%.1f, WindTo=%03d°, Theta=%03d°, Heading=%03d",v,vwind,windto,theta,intowind)) -return intowind,v -end -function NAVYGROUP:_FindPathToNextWaypoint() -self:T3(self.lid.."Path finding") -local astar=ASTAR:New() -local position=self:GetCoordinate() -local wpnext=self:GetWaypointNext() -if wpnext==nil then -return -end -local nextwp=wpnext.coordinate -if wpnext.intowind then -local hdg=self:GetHeading() -nextwp=position:Translate(UTILS.NMToMeters(20),hdg,true) -end -local speed=UTILS.MpsToKnots(wpnext.speed) -astar:SetStartCoordinate(position) -astar:SetEndCoordinate(nextwp) -local dist=position:Get2DDistance(nextwp) -if dist<5 then -return -end -local boxwidth=dist*2 -local spacex=dist*0.1 -local delta=dist/10 -astar:CreateGrid({land.SurfaceType.WATER},boxwidth,spacex,delta,delta,self.verbose>10) -astar:SetValidNeighbourLoS(self.pathCorridor) -local function findpath() -local path=astar:GetPath(true,true) -if path then -local uid=self:GetWaypointCurrent().uid -for i,_node in ipairs(path)do -local node=_node -local wp=self:AddWaypoint(node.coordinate,speed,uid) -wp.astar=true -uid=wp.uid -if self.verbose>=10 then -node.coordinate:MarkToAll(string.format("Path node #%d",i)) -end -end -return#path>0 -else -return false -end -end -return findpath() -end -OPERATION={ -ClassName="OPERATION", -verbose=0, -branches={}, -counterPhase=0, -counterBranch=0, -counterEdge=0, -cohorts={}, -legions={}, -targets={}, -missions={}, -} -_OPERATIONID=0 -OPERATION.PhaseStatus={ -PLANNED="Planned", -ACTIVE="Active", -OVER="Over", -} -OPERATION.version="0.2.0" -function OPERATION:New(Name) -local self=BASE:Inherit(self,FSM:New()) -_OPERATIONID=_OPERATIONID+1 -self.uid=_OPERATIONID -self.name=Name or string.format("Operation-%02d",_OPERATIONID) -self.lid=string.format("%s | ",self.name) -self:SetStartState("Planned") -self.branchMaster=self:AddBranch("Master") -self.conditionStart=CONDITION:New("Operation %s start",self.name) -self.conditionStart:SetNoneResult(false) -self.conditionStart:SetDefaultPersistence(false) -self.conditionOver=CONDITION:New("Operation %s over",self.name) -self.conditionOver:SetNoneResult(false) -self.conditionOver:SetDefaultPersistence(false) -self.branchActive=self.branchMaster -self:AddTransition("*","Start","Running") -self:AddTransition("*","StatusUpdate","*") -self:AddTransition("Running","Pause","Paused") -self:AddTransition("Paused","Unpause","Running") -self:AddTransition("*","PhaseOver","*") -self:AddTransition("*","PhaseNext","*") -self:AddTransition("*","PhaseChange","*") -self:AddTransition("*","BranchSwitch","*") -self:AddTransition("*","Over","Over") -self:AddTransition("*","Stop","Stopped") -self:__StatusUpdate(-1) -return self -end -function OPERATION:SetVerbosity(VerbosityLevel) -self.verbose=VerbosityLevel or 0 -return self -end -function OPERATION:SetTime(ClockStart,ClockStop) -local Tnow=timer.getAbsTime() -local Tstart=Tnow+5 -if ClockStart and type(ClockStart)=="number"then -Tstart=Tnow+ClockStart -elseif ClockStart and type(ClockStart)=="string"then -Tstart=UTILS.ClockToSeconds(ClockStart) -end -local Tstop=nil -if ClockStop and type(ClockStop)=="number"then -Tstop=Tnow+ClockStop -elseif ClockStop and type(ClockStop)=="string"then -Tstop=UTILS.ClockToSeconds(ClockStop) -end -self.Tstart=Tstart -self.Tstop=Tstop -if Tstop then -self.duration=self.Tstop-self.Tstart -end -return self -end -function OPERATION:AddConditonOverAll(Function,...) -local cf=self.conditionOver:AddFunctionAll(Function,...) -return cf -end -function OPERATION:AddConditonOverAny(Phase,Function,...) -local cf=self.conditionOver:AddFunctionAny(Function,...) -return cf -end -function OPERATION:AddPhase(Name,Branch,Duration) -Branch=Branch or self.branchMaster -local phase=self:_CreatePhase(Name) -phase.branch=Branch -phase.duration=Duration -self:T(self.lid..string.format("Adding phase %s to branch %s",phase.name,Branch.name)) -table.insert(Branch.phases,phase) -return phase -end -function OPERATION:InsertPhaseAfter(PhaseAfter,Name) -for i=1,#self.phases do -local phase=self.phases[i] -if PhaseAfter.uid==phase.uid then -local phase=self:_CreatePhase(Name) -end -end -return nil -end -function OPERATION:GetName() -return self.name or"Unknown" -end -function OPERATION:GetPhaseByName(Name) -for _,_branch in pairs(self.branches)do -local branch=_branch -for _,_phase in pairs(branch.phases or{})do -local phase=_phase -if phase.name==Name then -return phase -end -end -end -return nil -end -function OPERATION:SetPhaseStatus(Phase,Status) -if Phase then -self:T(self.lid..string.format("Phase %s status: %s-->%s",tostring(Phase.name),tostring(Phase.status),tostring(Status))) -Phase.status=Status -if Phase.status==OPERATION.PhaseStatus.ACTIVE then -Phase.Tstart=timer.getAbsTime() -Phase.nActive=Phase.nActive+1 -elseif Phase.status==OPERATION.PhaseStatus.OVER then -self:PhaseOver(Phase) -end -end -return self -end -function OPERATION:GetPhaseStatus(Phase) -return Phase.status -end -function OPERATION:SetPhaseConditonOver(Phase,Condition) -if Phase then -self:T(self.lid..string.format("Setting phase %s conditon over %s",self:GetPhaseName(Phase),Condition and Condition.name or"None")) -Phase.conditionOver=Condition -end -return self -end -function OPERATION:AddPhaseConditonOverAll(Phase,Function,...) -if Phase then -local cf=Phase.conditionOver:AddFunctionAll(Function,...) -return cf -end -return nil -end -function OPERATION:AddPhaseConditonOverAny(Phase,Function,...) -if Phase then -local cf=Phase.conditionOver:AddFunctionAny(Function,...) -return cf -end -return nil -end -function OPERATION:SetConditionFunctionPersistence(ConditionFunction,IsPersistent) -ConditionFunction.persistence=IsPersistent -return self -end -function OPERATION:AddPhaseConditonRepeatAll(Phase,Function,...) -if Phase then -Phase.conditionRepeat:AddFunctionAll(Function,...) -end -return self -end -function OPERATION:GetPhaseConditonOver(Phase,Condition) -return Phase.conditionOver -end -function OPERATION:GetPhaseNactive(Phase) -return Phase.nActive -end -function OPERATION:GetPhaseName(Phase) -Phase=Phase or self.phase -if Phase then -return Phase.name -end -return"None" -end -function OPERATION:GetPhaseActive() -return self.phase -end -function OPERATION:GetPhaseIndex(Phase) -local branch=Phase.branch -for i,_phase in pairs(branch.phases)do -local phase=_phase -if phase.uid==Phase.uid then -return i,branch -end -end -return nil -end -function OPERATION:GetPhaseNext(Branch,PhaseStatus) -Branch=Branch or self:GetBranchActive() -local phases=Branch.phases or{} -local phase=nil -if self.phase and self.phase.branch.uid==Branch.uid then -phase=self.phase -end -local N=#phases -self:T(self.lid..string.format("Getting next phase! Branch=%s, Phases=%d, Status=%s",Branch.name,N,tostring(PhaseStatus))) -if N>0 then -if phase==nil and PhaseStatus==nil then -return phases[1] -end -local n=1 -if phase then -n=self:GetPhaseIndex(phase)+1 -end -for i=n,N do -local phase=phases[i] -if PhaseStatus==nil or PhaseStatus==phase.status then -return phase -end -end -end -return nil -end -function OPERATION:CountPhases(Status,Branch) -Branch=Branch or self.branchActive -local N=0 -for _,_phase in pairs(Branch.phases)do -local phase=_phase -if Status==nil or Status==phase.status then -N=N+1 -end -end -return N -end -function OPERATION:AddBranch(Name) -local branch=self:_CreateBranch(Name) -table.insert(self.branches,branch) -return branch -end -function OPERATION:GetBranchMaster() -return self.branchMaster -end -function OPERATION:GetBranchActive() -return self.branchActive or self.branchMaster -end -function OPERATION:GetBranchName(Branch) -Branch=Branch or self:GetBranchActive() -if Branch then -return Branch.name -end -return"None" -end -function OPERATION:AddEdge(PhaseFrom,PhaseTo,ConditionSwitch) -local edge={} -edge.phaseFrom=PhaseFrom -edge.phaseTo=PhaseTo -edge.branchFrom=PhaseFrom.branch -edge.branchTo=PhaseTo.branch -if ConditionSwitch then -edge.conditionSwitch=ConditionSwitch -else -edge.conditionSwitch=CONDITION:New("Edge") -edge.conditionSwitch:SetNoneResult(true) -end -table.insert(edge.branchFrom.edges,edge) -return edge -end -function OPERATION:AddEdgeConditonSwitchAll(Edge,Function,...) -if Edge then -local cf=Edge.conditionSwitch:AddFunctionAll(Function,...) -return cf -end -return nil -end -function OPERATION:AddMission(Mission,Phase) -Mission.phase=Phase -Mission.operation=self -table.insert(self.missions,Mission) -return self -end -function OPERATION:AddTarget(Target,Phase) -Target.phase=Phase -Target.operation=self -table.insert(self.targets,Target) -return self -end -function OPERATION:GetTargets(Phase) -local N={} -for _,_target in pairs(self.targets)do -local target=_target -if target:IsAlive()and(Phase==nil or target.phase==Phase)then -table.insert(N,target) -end -end -return N -end -function OPERATION:CountTargets(Phase) -local N=0 -for _,_target in pairs(self.targets)do -local target=_target -if target:IsAlive()and(Phase==nil or target.phase==Phase)then -N=N+1 -end -end -return N -end -function OPERATION:AssignCohort(Cohort) -self:T(self.lid..string.format("Assiging Cohort %s to operation",Cohort.name)) -self.cohorts[Cohort.name]=Cohort -end -function OPERATION:AssignLegion(Legion) -self.legions[Legion.alias]=Legion -end -function OPERATION:IsAssignedLegion(Legion) -local legion=self.legions[Legion.alias] -if legion then -self:T(self.lid..string.format("Legion %s is assigned to this operation",Legion.alias)) -return true -else -self:T(self.lid..string.format("Legion %s is NOT assigned to this operation",Legion.alias)) -return false -end -end -function OPERATION:IsAssignedCohort(Cohort) -local cohort=self.cohorts[Cohort.name] -if cohort then -self:T(self.lid..string.format("Cohort %s is assigned to this operation",Cohort.name)) -return true -else -local Legion=Cohort.legion -if Legion and self:IsAssignedLegion(Legion)then -self:T(self.lid..string.format("Legion %s of Cohort %s is assigned to this operation",Legion.alias,Cohort.name)) -return true -end -self:T(self.lid..string.format("Cohort %s is NOT assigned to this operation",Cohort.name)) -return false -end -return nil -end -function OPERATION:IsAssignedCohortOrLegion(Object) -local isAssigned=nil -if Object:IsInstanceOf("COHORT")then -isAssigned=self:IsAssignedCohort(Object) -elseif Object:IsInstanceOf("LEGION")then -isAssigned=self:IsAssignedLegion(Object) -else -self:E(self.lid.."ERROR: Unknown Object!") -end -return isAssigned -end -function OPERATION:IsPlanned() -local is=self:is("Planned") -return is -end -function OPERATION:IsRunning() -local is=self:is("Running") -return is -end -function OPERATION:IsPaused() -local is=self:is("Paused") -return is -end -function OPERATION:IsOver() -local is=self:is("Over") -return is -end -function OPERATION:IsStopped() -local is=self:is("Stopped") -return is -end -function OPERATION:IsNotOver() -local is=not(self:IsOver()or self:IsStopped()) -return is -end -function OPERATION:IsPhaseActive(Phase) -if Phase and Phase.status and Phase.status==OPERATION.PhaseStatus.ACTIVE then -return true -end -return false -end -function OPERATION:IsPhaseActive(Phase) -local phase=self:GetPhaseActive() -if phase and phase.uid==Phase.uid then -return true -else -return false -end -return nil -end -function OPERATION:IsPhasePlanned(Phase) -if Phase and Phase.status and Phase.status==OPERATION.PhaseStatus.PLANNED then -return true -end -return false -end -function OPERATION:IsPhaseOver(Phase) -if Phase and Phase.status and Phase.status==OPERATION.PhaseStatus.OVER then -return true -end -return false -end -function OPERATION:onafterStart(From,Event,To) -self:T(self.lid..string.format("Starting Operation!")) -return self -end -function OPERATION:onafterStatusUpdate(From,Event,To) -local Tnow=timer.getAbsTime() -local fsmstate=self:GetState() -if self:IsPlanned()then -if(self.Tstart and Tnow>self.Tstart or self.Tstart==nil)and(self.conditionStart==nil or self.conditionStart:Evaluate())then -self:Start() -end -elseif self:IsNotOver()then -if(self.Tstop and Tnow>self.Tstop or self.Tstop==nil)and(self.conditionOver==nil or self.conditionOver:Evaluate())then -self:Over() -end -end -if self:IsRunning()then -self:_CheckPhases() -end -if self.verbose>=1 then -local phaseName=self:GetPhaseName() -local branchName=self:GetBranchName() -local NphaseTot=self:CountPhases() -local NphaseAct=self:CountPhases(OPERATION.PhaseStatus.ACTIVE) -local NphasePla=self:CountPhases(OPERATION.PhaseStatus.PLANNED) -local NphaseOvr=self:CountPhases(OPERATION.PhaseStatus.OVER) -local text=string.format("State=%s: Phase=%s [%s], Phases=%d [Active=%d, Planned=%d, Over=%d]",fsmstate,phaseName,branchName,NphaseTot,NphaseAct,NphasePla,NphaseOvr) -self:I(self.lid..text) -end -if self.verbose>=2 then -local text="Phases:" -for i,_phase in pairs(self.branchActive.phases)do -local phase=_phase -text=text..string.format("\n[%d] %s [uid=%d]: status=%s Nact=%d",i,phase.name,phase.uid,tostring(phase.status),phase.nActive) -end -if text=="Phases:"then text=text.." None"end -self:I(self.lid..text) -end -self:__StatusUpdate(-30) -return self -end -function OPERATION:onafterPhaseNext(From,Event,To) -local Phase=self:GetPhaseNext() -if Phase then -self:PhaseChange(Phase) -else -self:Over() -end -return self -end -function OPERATION:onafterPhaseChange(From,Event,To,Phase) -local oldphase="None" -if self.phase then -if self.phase.status~=OPERATION.PhaseStatus.OVER then -self:SetPhaseStatus(self.phase,OPERATION.PhaseStatus.OVER) -end -oldphase=self.phase.name -end -self:I(self.lid..string.format("Phase change: %s --> %s",oldphase,Phase.name)) -self.phase=Phase -self:SetPhaseStatus(Phase,OPERATION.PhaseStatus.ACTIVE) -return self -end -function OPERATION:onafterPhaseOver(From,Event,To,Phase) -Phase.conditionOver:RemoveNonPersistant() -end -function OPERATION:onafterBranchSwitch(From,Event,To,Branch,Phase) -self:T(self.lid..string.format("Switching to branch %s",Branch.name)) -self.branchActive=Branch -self:PhaseChange(Phase) -return self -end -function OPERATION:onafterOver(From,Event,To) -self:T(self.lid..string.format("Operation is over!")) -self.phase=nil -for _,_branch in pairs(self.branches)do -local branch=_branch -for _,_phase in pairs(branch.phases)do -local phase=_phase -if not self:IsPhaseOver(phase)then -self:SetPhaseStatus(phase,OPERATION.PhaseStatus.OVER) -end -end -end -return self -end -function OPERATION:_CheckPhases() -local phase=self:GetPhaseActive() -if phase and phase.conditionOver then -local isOver=phase.conditionOver:Evaluate() -local Tnow=timer.getAbsTime() -if phase.duration and phase.Tstart and Tnow-phase.Tstart>phase.duration then -isOver=true -end -if isOver then -self:SetPhaseStatus(phase,OPERATION.PhaseStatus.OVER) -end -end -if phase==nil or phase.status==OPERATION.PhaseStatus.OVER then -for _,_edge in pairs(self.branchActive.edges)do -local edge=_edge -if phase then -end -if(edge.phaseFrom==nil)or(phase and edge.phaseFrom.uid==phase.uid)then -local switch=edge.conditionSwitch:Evaluate() -if switch then -local phaseTo=edge.phaseTo or self:GetPhaseNext(edge.branchTo,nil) -if phaseTo then -self:BranchSwitch(edge.branchTo,phaseTo) -else -self:Over() -end -return -end -end -end -self:PhaseNext() -end -end -function OPERATION:_CreatePhase(Name) -self.counterPhase=self.counterPhase+1 -local phase={} -phase.uid=self.counterPhase -phase.name=Name or string.format("Phase-%02d",self.counterPhase) -phase.conditionOver=CONDITION:New(Name.." Over") -phase.conditionOver:SetDefaultPersistence(false) -phase.status=OPERATION.PhaseStatus.PLANNED -phase.nActive=0 -return phase -end -function OPERATION:_CreateBranch(Name) -self.counterBranch=self.counterBranch+1 -local branch={} -branch.uid=self.counterBranch -branch.name=Name or string.format("Branch-%02d",self.counterBranch) -branch.phases={} -branch.edges={} -return branch -end -OPSGROUP={ -ClassName="OPSGROUP", -verbose=0, -lid=nil, -groupname=nil, -group=nil, -template=nil, -isLateActivated=nil, -waypoints=nil, -waypoints0=nil, -currentwp=1, -elements={}, -taskqueue={}, -taskcounter=nil, -taskcurrent=nil, -taskenroute=nil, -taskpaused={}, -missionqueue={}, -currentmission=nil, -detectedunits={}, -detectedgroups={}, -attribute=nil, -checkzones=nil, -inzones=nil, -groupinitialized=nil, -wpcounter=1, -radio={}, -option={}, -optionDefault={}, -tacan={}, -icls={}, -callsign={}, -Ndestroyed=0, -Nkills=0, -Nhit=0, -weaponData={}, -cargoqueue={}, -cargoBay={}, -mycarrier={}, -carrierLoader={}, -carrierUnloader={}, -useMEtasks=false, -pausedmissions={}, -} -OPSGROUP.ElementStatus={ -INUTERO="InUtero", -SPAWNED="Spawned", -PARKING="Parking", -ENGINEON="Engine On", -TAXIING="Taxiing", -TAKEOFF="Takeoff", -AIRBORNE="Airborne", -LANDING="Landing", -LANDED="Landed", -ARRIVED="Arrived", -DEAD="Dead", -} -OPSGROUP.GroupStatus={ -INUTERO="InUtero", -PARKING="Parking", -TAXIING="Taxiing", -AIRBORNE="Airborne", -INBOUND="Inbound", -LANDING="Landing", -LANDED="Landed", -ARRIVED="Arrived", -DEAD="Dead", -} -OPSGROUP.TaskStatus={ -SCHEDULED="scheduled", -EXECUTING="executing", -PAUSED="paused", -DONE="done", -} -OPSGROUP.TaskType={ -SCHEDULED="scheduled", -WAYPOINT="waypoint", -} -OPSGROUP.CarrierStatus={ -NOTCARRIER="not carrier", -PICKUP="pickup", -LOADING="loading", -LOADED="loaded", -TRANSPORTING="transporting", -UNLOADING="unloading", -} -OPSGROUP.CargoStatus={ -AWAITING="Awaiting carrier", -NOTCARGO="not cargo", -ASSIGNED="assigned to carrier", -BOARDING="boarding", -LOADED="loaded", -} -OPSGROUP.version="1.0.0" -function OPSGROUP:New(group) -local self=BASE:Inherit(self,FSM:New()) -if type(group)=="string"then -self.groupname=group -self.group=GROUP:FindByName(self.groupname) -else -self.group=group -self.groupname=group:GetName() -end -self.lid=string.format("OPSGROUP %s | ",tostring(self.groupname)) -if self.group then -if not self:IsExist()then -self:T(self.lid.."ERROR: GROUP does not exist! Returning nil") -return nil -end -end -self:_SetTemplate() -self.dcsgroup=self:GetDCSGroup() -self.controller=self.dcsgroup:getController() -self.category=self.dcsgroup:getCategory() -if self.category==Group.Category.GROUND then -self.isArmygroup=true -elseif self.category==Group.Category.TRAIN then -self.isArmygroup=true -self.isTrain=true -elseif self.category==Group.Category.SHIP then -self.isNavygroup=true -elseif self.category==Group.Category.AIRPLANE then -self.isFlightgroup=true -elseif self.category==Group.Category.HELICOPTER then -self.isFlightgroup=true -self.isHelo=true -else -end -self.attribute=self.group:GetAttribute() -local units=self.group:GetUnits() -if units then -local masterunit=units[1] -self.descriptors=masterunit:GetDesc() -self.actype=masterunit:GetTypeName() -self.isSubmarine=masterunit:HasAttribute("Submarines") -self.isEPLRS=masterunit:HasAttribute("Datalink") -if self:IsFlightgroup()then -self.rangemax=self.descriptors.range and self.descriptors.range*1000 or 500*1000 -self.ceiling=self.descriptors.Hmax -self.tankertype=select(2,masterunit:IsTanker()) -self.refueltype=select(2,masterunit:IsRefuelable()) -end -end -self.detectedunits=SET_UNIT:New() -self.detectedgroups=SET_GROUP:New() -self.inzones=SET_ZONE:New() -self:SetDefaultAltitude() -self:SetReturnToLegion() -self.spot={} -self.spot.On=false -self.spot.timer=TIMER:New(self._UpdateLaser,self) -self.spot.Coordinate=COORDINATE:New(0,0,0) -self:SetLaser(1688,true,false,0.5) -self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO -self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER -self:SetCarrierLoaderAllAspect() -self:SetCarrierUnloaderAllAspect() -self.taskcurrent=0 -self.taskcounter=0 -self:SetStartState("InUtero") -self:AddTransition("InUtero","Spawned","Spawned") -self:AddTransition("*","Respawn","InUtero") -self:AddTransition("*","Dead","InUtero") -self:AddTransition("*","InUtero","InUtero") -self:AddTransition("*","Stop","Stopped") -self:AddTransition("*","Hit","*") -self:AddTransition("*","Damaged","*") -self:AddTransition("*","Destroyed","*") -self:AddTransition("*","UpdateRoute","*") -self:AddTransition("*","PassingWaypoint","*") -self:AddTransition("*","PassedFinalWaypoint","*") -self:AddTransition("*","GotoWaypoint","*") -self:AddTransition("*","Wait","*") -self:AddTransition("*","DetectedUnit","*") -self:AddTransition("*","DetectedUnitNew","*") -self:AddTransition("*","DetectedUnitKnown","*") -self:AddTransition("*","DetectedUnitLost","*") -self:AddTransition("*","DetectedGroup","*") -self:AddTransition("*","DetectedGroupNew","*") -self:AddTransition("*","DetectedGroupKnown","*") -self:AddTransition("*","DetectedGroupLost","*") -self:AddTransition("*","OutOfAmmo","*") -self:AddTransition("*","OutOfGuns","*") -self:AddTransition("*","OutOfRockets","*") -self:AddTransition("*","OutOfBombs","*") -self:AddTransition("*","OutOfMissiles","*") -self:AddTransition("*","OutOfTorpedos","*") -self:AddTransition("*","OutOfMissilesAA","*") -self:AddTransition("*","OutOfMissilesAG","*") -self:AddTransition("*","OutOfMissilesAS","*") -self:AddTransition("*","EnterZone","*") -self:AddTransition("*","LeaveZone","*") -self:AddTransition("*","LaserOn","*") -self:AddTransition("*","LaserOff","*") -self:AddTransition("*","LaserCode","*") -self:AddTransition("*","LaserPause","*") -self:AddTransition("*","LaserResume","*") -self:AddTransition("*","LaserLostLOS","*") -self:AddTransition("*","LaserGotLOS","*") -self:AddTransition("*","TaskExecute","*") -self:AddTransition("*","TaskPause","*") -self:AddTransition("*","TaskCancel","*") -self:AddTransition("*","TaskDone","*") -self:AddTransition("*","MissionStart","*") -self:AddTransition("*","MissionExecute","*") -self:AddTransition("*","MissionCancel","*") -self:AddTransition("*","PauseMission","*") -self:AddTransition("*","UnpauseMission","*") -self:AddTransition("*","MissionDone","*") -self:AddTransition("*","ElementInUtero","*") -self:AddTransition("*","ElementSpawned","*") -self:AddTransition("*","ElementDestroyed","*") -self:AddTransition("*","ElementDead","*") -self:AddTransition("*","ElementDamaged","*") -self:AddTransition("*","ElementHit","*") -self:AddTransition("*","Board","*") -self:AddTransition("*","Embarked","*") -self:AddTransition("*","Disembarked","*") -self:AddTransition("*","Pickup","*") -self:AddTransition("*","Loading","*") -self:AddTransition("*","Load","*") -self:AddTransition("*","Loaded","*") -self:AddTransition("*","LoadingDone","*") -self:AddTransition("*","Transport","*") -self:AddTransition("*","Unloading","*") -self:AddTransition("*","Unload","*") -self:AddTransition("*","Unloaded","*") -self:AddTransition("*","UnloadingDone","*") -self:AddTransition("*","Delivered","*") -self:AddTransition("*","TransportCancel","*") -self:AddTransition("*","HoverStart","*") -self:AddTransition("*","HoverEnd","*") -return self -end -function OPSGROUP:GetCoalition() -return self.group:GetCoalition() -end -function OPSGROUP:GetLifePoints(Element) -local life=0 -local life0=0 -if Element then -local unit=Element.unit -if unit then -life=unit:GetLife() -life0=unit:GetLife0() -life=math.min(life,life0) -end -else -for _,element in pairs(self.elements)do -local l,l0=self:GetLifePoints(element) -life=life+l -life0=life0+l0 -end -end -return life,life0 -end -function OPSGROUP:GetAttribute() -return self.attribute -end -function OPSGROUP:SetVerbosity(VerbosityLevel) -self.verbose=VerbosityLevel or 0 -return self -end -function OPSGROUP:_SetLegion(Legion) -self:T2(self.lid..string.format("Adding opsgroup to legion %s",Legion.alias)) -self.legion=Legion -return self -end -function OPSGROUP:SetReturnToLegion(Switch) -if Switch==false then -self.legionReturn=false -else -self.legionReturn=true -end -self:T(self.lid..string.format("Setting ReturnToLetion=%s",tostring(self.legionReturn))) -return self -end -function OPSGROUP:SetDefaultSpeed(Speed) -if Speed then -self.speedCruise=UTILS.KnotsToKmph(Speed) -end -return self -end -function OPSGROUP:GetSpeedCruise() -local speed=UTILS.KmphToKnots(self.speedCruise or self.speedMax*0.7) -return speed -end -function OPSGROUP:SetDefaultAltitude(Altitude) -if Altitude then -self.altitudeCruise=UTILS.FeetToMeters(Altitude) -else -if self:IsFlightgroup()then -if self.isHelo then -self.altitudeCruise=UTILS.FeetToMeters(1500) -else -self.altitudeCruise=UTILS.FeetToMeters(10000) -end -else -self.altitudeCruise=0 -end -end -return self -end -function OPSGROUP:GetCruiseAltitude() -local alt=UTILS.MetersToFeet(self.altitudeCruise) -return alt -end -function OPSGROUP:SetAltitude(Altitude,Keep,RadarAlt) -if Altitude then -Altitude=UTILS.FeetToMeters(Altitude) -else -if self:IsFlightgroup()then -if self.isHelo then -Altitude=UTILS.FeetToMeters(1500) -else -Altitude=UTILS.FeetToMeters(10000) -end -else -Altitude=0 -end -end -local AltType="BARO" -if RadarAlt then -AltType="RADIO" -end -if self.controller then -self.controller:setAltitude(Altitude,Keep,AltType) -end -return self -end -function OPSGROUP:GetAltitude() -local alt=0 -if self.group then -alt=self.group:GetAltitude() -alt=UTILS.MetersToFeet(alt) -end -return alt -end -function OPSGROUP:SetSpeed(Speed,Keep,AltCorrected) -if Speed then -else -Speed=UTILS.KmphToKnots(self.speedMax) -end -if AltCorrected then -local altitude=self:GetAltitude() -Speed=UTILS.KnotsToAltKIAS(Speed,altitude) -end -Speed=UTILS.KnotsToMps(Speed) -if self.controller then -self.controller:setSpeed(Speed,Keep) -end -return self -end -function OPSGROUP:SetDetection(Switch) -self:T(self.lid..string.format("Detection is %s",tostring(Switch))) -self.detectionOn=Switch -return self -end -function OPSGROUP:GetDCSObject() -return self.dcsgroup -end -function OPSGROUP:KnowTarget(TargetObject,KnowType,KnowDist,Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,OPSGROUP.KnowTarget,self,TargetObject,KnowType,KnowDist,0) -else -if TargetObject:IsInstanceOf("GROUP")then -TargetObject=TargetObject:GetUnit(1) -elseif TargetObject:IsInstanceOf("OPSGROUP")then -TargetObject=TargetObject.group:GetUnit(1) -end -local object=TargetObject:GetDCSObject() -for _,_element in pairs(self.elements)do -local element=_element -if element.controller then -element.controller:knowTarget(object,true,true) -end -end -self:T(self.lid..string.format("We should now know target %s",TargetObject:GetName())) -end -return self -end -function OPSGROUP:IsTargetDetected(TargetObject) -local objects={} -if TargetObject:IsInstanceOf("GROUP")then -for _,unit in pairs(TargetObject:GetUnits())do -table.insert(objects,unit:GetDCSObject()) -end -elseif TargetObject:IsInstanceOf("OPSGROUP")then -for _,unit in pairs(TargetObject.group:GetUnits())do -table.insert(objects,unit:GetDCSObject()) -end -elseif TargetObject:IsInstanceOf("UNIT")or TargetObject:IsInstanceOf("STATIC")then -table.insert(objects,TargetObject:GetDCSObject()) -end -for _,object in pairs(objects or{})do -local detected,visible,lastTime,type,distance,lastPos,lastVel=self.controller:isTargetDetected(object,1,2,4,8,16,32) -if detected then -return true -end -for _,_element in pairs(self.elements)do -local element=_element -if element.controller then -local detected,visible,lastTime,type,distance,lastPos,lastVel= -element.controller:isTargetDetected(object,1,2,4,8,16,32) -if detected then -return true -end -end -end -end -return false -end -function OPSGROUP:InWeaponRange(TargetCoord,WeaponBitType,RefCoord) -RefCoord=RefCoord or self:GetCoordinate() -local dist=TargetCoord:Get2DDistance(RefCoord) -if WeaponBitType then -local weapondata=self:GetWeaponData(WeaponBitType) -if weapondata then -if dist>=weapondata.RangeMin and dist<=weapondata.RangeMax then -return true -else -return false -end -end -else -for _,_weapondata in pairs(self.weaponData or{})do -local weapondata=_weapondata -if dist>=weapondata.RangeMin and dist<=weapondata.RangeMax then -return true -end -end -return false -end -return nil -end -function OPSGROUP:GetCoordinateInRange(TargetCoord,WeaponBitType,RefCoord) -local coordInRange=nil -RefCoord=RefCoord or self:GetCoordinate() -local weapondata=self:GetWeaponData(WeaponBitType) -if weapondata then -local heading=RefCoord:HeadingTo(TargetCoord) -local dist=RefCoord:Get2DDistance(TargetCoord) -if dist>weapondata.RangeMax then -local d=(dist-weapondata.RangeMax)*1.05 -coordInRange=RefCoord:Translate(d,heading) -self:T(self.lid..string.format("Out of max range = %.1f km for weapon %s",weapondata.RangeMax/1000,tostring(WeaponBitType))) -elseif dist=ThreatLevelMin and threatlevel<=ThreatLevelMax then -if threatlevellevelmax then -threat=unit -levelmax=threatlevel -end -end -return threat,levelmax -end -function OPSGROUP:SetEngageDetectedOn(RangeMax,TargetTypes,EngageZoneSet,NoEngageZoneSet) -if TargetTypes then -if type(TargetTypes)~="table"then -TargetTypes={TargetTypes} -end -else -TargetTypes={"All"} -end -if EngageZoneSet and EngageZoneSet:IsInstanceOf("ZONE_BASE")then -local zoneset=SET_ZONE:New():AddZone(EngageZoneSet) -EngageZoneSet=zoneset -end -if NoEngageZoneSet and NoEngageZoneSet:IsInstanceOf("ZONE_BASE")then -local zoneset=SET_ZONE:New():AddZone(NoEngageZoneSet) -NoEngageZoneSet=zoneset -end -self.engagedetectedOn=true -self.engagedetectedRmax=UTILS.NMToMeters(RangeMax or 25) -self.engagedetectedTypes=TargetTypes -self.engagedetectedEngageZones=EngageZoneSet -self.engagedetectedNoEngageZones=NoEngageZoneSet -self:T(self.lid..string.format("Engage detected ON: Rmax=%d NM",UTILS.MetersToNM(self.engagedetectedRmax))) -self:SetDetection(true) -return self -end -function OPSGROUP:SetEngageDetectedOff() -self:T(self.lid..string.format("Engage detected OFF")) -self.engagedetectedOn=false -return self -end -function OPSGROUP:SetRearmOnOutOfAmmo() -self.rearmOnOutOfAmmo=true -return self -end -function OPSGROUP:SetRetreatOnOutOfAmmo() -self.retreatOnOutOfAmmo=true -return self -end -function OPSGROUP:SetReturnOnOutOfAmmo() -self.rtzOnOutOfAmmo=true -return self -end -function OPSGROUP:SetCargoBayLimit(Weight,UnitName) -for _,_element in pairs(self.elements)do -local element=_element -if UnitName==nil or UnitName==element.name then -element.weightMaxCargo=Weight -if element.unit then -element.unit:SetCargoBayWeightLimit(Weight) -end -end -end -return self -end -function OPSGROUP:HasLoS(Coordinate,Element,OffsetElement,OffsetCoordinate) -if Coordinate then -local Vec3={x=Coordinate.x,y=Coordinate.y,z=Coordinate.z} -if OffsetCoordinate then -Vec3=UTILS.VecAdd(Vec3,OffsetCoordinate) -end -local function checklos(vec3) -if vec3 then -if OffsetElement then -vec3=UTILS.VecAdd(vec3,OffsetElement) -end -local _los=land.isVisible(vec3,Vec3) -return _los -end -return nil -end -if Element then -if Element.unit and Element.unit:IsAlive()then -local vec3=Element.unit:GetVec3() -local los=checklos(vec3) -return los -end -else -local gotit=false -for _,_element in pairs(self.elements)do -local element=_element -if element and element.unit and element.unit:IsAlive()then -gotit=true -local vec3=element.unit:GetVec3() -local los=checklos(vec3) -if los then -return true -end -end -end -if gotit then -return false -end -end -end -return nil -end -function OPSGROUP:GetGroup() -return self.group -end -function OPSGROUP:GetName() -return self.groupname -end -function OPSGROUP:GetDCSGroup() -local DCSGroup=Group.getByName(self.groupname) -return DCSGroup -end -function OPSGROUP:GetUnit(UnitNumber) -local DCSUnit=self:GetDCSUnit(UnitNumber) -if DCSUnit then -local unit=UNIT:Find(DCSUnit) -return unit -end -return nil -end -function OPSGROUP:GetDCSUnit(UnitNumber) -local DCSGroup=self:GetDCSGroup() -if DCSGroup then -local unit=DCSGroup:getUnit(UnitNumber or 1) -return unit -end -return nil -end -function OPSGROUP:GetDCSUnits() -local DCSGroup=self:GetDCSGroup() -if DCSGroup then -local units=DCSGroup:getUnits() -return units -end -return nil -end -function OPSGROUP:GetVec2(UnitName) -local vec3=self:GetVec3(UnitName) -if vec3 then -local vec2={x=vec3.x,y=vec3.z} -return vec2 -end -return nil -end -function OPSGROUP:GetVec3(UnitName) -local vec3=nil -local carrier=self:_GetMyCarrierElement() -if carrier and carrier.status~=OPSGROUP.ElementStatus.DEAD and self:IsLoaded()then -local unit=carrier.unit -if unit and unit:IsExist()then -vec3=unit:GetVec3() -return vec3 -end -end -if self:IsExist()then -local unit=nil -if UnitName then -unit=Unit.getByName(UnitName) -else -unit=self:GetDCSUnit() -end -if unit then -local vec3=unit:getPoint() -return vec3 -end -end -if self.position then -return self.position -end -return nil -end -function OPSGROUP:GetCoordinate(NewObject,UnitName) -local vec3=self:GetVec3(UnitName)or self.position -if vec3 then -self.coordinate=self.coordinate or COORDINATE:New(0,0,0) -self.coordinate.x=vec3.x -self.coordinate.y=vec3.y -self.coordinate.z=vec3.z -if NewObject then -local coord=COORDINATE:NewFromCoordinate(self.coordinate) -return coord -else -return self.coordinate -end -else -self:T(self.lid.."WARNING: Cannot get coordinate!") -end -return nil -end -function OPSGROUP:GetVelocity(UnitName) -if self:IsExist()then -local unit=nil -if UnitName then -unit=Unit.getByName(UnitName) -else -unit=self:GetDCSUnit() -end -if unit then -local velvec3=unit:getVelocity() -local vel=UTILS.VecNorm(velvec3) -return vel -else -self:T(self.lid.."WARNING: Unit does not exist. Cannot get velocity!") -end -else -self:T(self.lid.."WARNING: Group does not exist. Cannot get velocity!") -end -return nil -end -function OPSGROUP:GetHeading(UnitName) -if self:IsExist()then -local unit=nil -if UnitName then -unit=Unit.getByName(UnitName) -else -unit=self:GetDCSUnit() -end -if unit then -local pos=unit:getPosition() -local heading=math.atan2(pos.x.z,pos.x.x) -if heading<0 then -heading=heading+2*math.pi -end -heading=math.deg(heading) -return heading -end -else -self:T(self.lid.."WARNING: Group does not exist. Cannot get heading!") -end -return nil -end -function OPSGROUP:GetOrientation(UnitName) -if self:IsExist()then -local unit=nil -if UnitName then -unit=Unit.getByName(UnitName) -else -unit=self:GetDCSUnit() -end -if unit then -local pos=unit:getPosition() -return pos.x,pos.y,pos.z -end -else -self:T(self.lid.."WARNING: Group does not exist. Cannot get orientation!") -end -return nil -end -function OPSGROUP:GetOrientationX(UnitName) -local X,Y,Z=self:GetOrientation(UnitName) -return X -end -function OPSGROUP:CheckTaskDescriptionUnique(description) -for _,_task in pairs(self.taskqueue)do -local task=_task -if task.description==description then -return false -end -end -return true -end -function OPSGROUP:DespawnUnit(UnitName,Delay,NoEventRemoveUnit) -self:T(self.lid.."Despawn element "..tostring(UnitName)) -local element=self:GetElementByName(UnitName) -if element then -local DCSunit=Unit.getByName(UnitName) -if DCSunit then -DCSunit:destroy() -self:ElementInUtero(element) -if not NoEventRemoveUnit then -self:CreateEventRemoveUnit(timer.getTime(),DCSunit) -end -end -end -end -function OPSGROUP:DespawnElement(Element,Delay,NoEventRemoveUnit) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,OPSGROUP.DespawnElement,self,Element,0,NoEventRemoveUnit) -else -if Element then -local DCSunit=Unit.getByName(Element.name) -if DCSunit then -DCSunit:destroy() -if not NoEventRemoveUnit then -self:CreateEventRemoveUnit(timer.getTime(),DCSunit) -end -end -end -end -return self -end -function OPSGROUP:Despawn(Delay,NoEventRemoveUnit) -if Delay and Delay>0 then -self.scheduleIDDespawn=self:ScheduleOnce(Delay,OPSGROUP.Despawn,self,0,NoEventRemoveUnit) -else -self:T(self.lid..string.format("Despawning Group!")) -local DCSGroup=self:GetDCSGroup() -if DCSGroup then -local units=self:GetDCSUnits() -for i=1,#units do -local unit=units[i] -if unit then -local name=unit:getName() -if name then -self:DespawnUnit(name,0,NoEventRemoveUnit) -end -end -end -end -end -return self -end -function OPSGROUP:ReturnToLegion(Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,OPSGROUP.ReturnToLegion,self) -else -if self.legion then -self:T(self.lid..string.format("Adding asset back to LEGION")) -self.legion:AddAsset(self.group,1) -else -self:E(self.lid..string.format("ERROR: Group does not belong to a LEGION!")) -end -end -return self -end -function OPSGROUP:DestroyUnit(UnitName,Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,OPSGROUP.DestroyUnit,self,UnitName,0) -else -local unit=Unit.getByName(UnitName) -if unit then -local EventTime=timer.getTime() -if self:IsFlightgroup()then -self:CreateEventUnitLost(EventTime,unit) -else -self:CreateEventDead(EventTime,unit) -end -unit:destroy() -end -end -end -function OPSGROUP:Destroy(Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,OPSGROUP.Destroy,self,0) -else -local units=self:GetDCSUnits() -if units then -for _,unit in pairs(units)do -if unit then -self:DestroyUnit(unit:getName()) -end -end -end -end -return self -end -function OPSGROUP:Activate(delay) -if delay and delay>0 then -self:T2(self.lid..string.format("Activating late activated group in %d seconds",delay)) -self:ScheduleOnce(delay,OPSGROUP.Activate,self) -else -if self:IsAlive()==false then -self:T(self.lid.."Activating late activated group") -self.group:Activate() -self.isLateActivated=false -elseif self:IsAlive()==true then -self:T(self.lid.."WARNING: Activating group that is already activated") -else -self:T(self.lid.."ERROR: Activating group that is does not exist!") -end -end -return self -end -function OPSGROUP:Deactivate(delay) -if delay and delay>0 then -self:ScheduleOnce(delay,OPSGROUP.Deactivate,self) -else -if self:IsAlive()==true then -self.template.lateActivation=true -local template=UTILS.DeepCopy(self.template) -self:_Respawn(0,template) -end -end -return self -end -function OPSGROUP:SelfDestruction(Delay,ExplosionPower,ElementName) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,OPSGROUP.SelfDestruction,self,0,ExplosionPower,ElementName) -else -for i,_element in pairs(self.elements)do -local element=_element -if ElementName==nil or ElementName==element.name then -local unit=element.unit -if unit and unit:IsAlive()then -unit:Explode(ExplosionPower or 100) -end -end -end -end -return self -end -function OPSGROUP:SetSRS(PathToSRS,Gender,Culture,Voice,Port,PathToGoogleKey,Label,Volume) -self.useSRS=true -local path=PathToSRS or MSRS.path -local port=Port or MSRS.port -self.msrs=MSRS:New(path,self.frequency,self.modulation) -self.msrs:SetGender(Gender) -self.msrs:SetCulture(Culture) -self.msrs:SetVoice(Voice) -self.msrs:SetPort(port) -self.msrs:SetLabel(Label) -if PathToGoogleKey then -self.msrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) -self.msrs:SetProvider(MSRS.Provider.GOOGLE) -end -self.msrs:SetCoalition(self:GetCoalition()) -self.msrs:SetVolume(Volume) -return self -end -function OPSGROUP:RadioTransmission(Text,Delay,SayCallsign,Frequency) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,OPSGROUP.RadioTransmission,self,Text,0,SayCallsign) -else -if self.useSRS and self.msrs then -local freq,modu,radioon=self:GetRadio() -local coord=self:GetCoordinate() -self.msrs:SetCoordinate(coord) -if Frequency then -self.msrs:SetFrequencies(Frequency) -else -self.msrs:SetFrequencies(freq) -end -self.msrs:SetModulations(modu) -if SayCallsign then -local callsign=self:GetCallsignName() -Text=string.format("%s, %s",callsign,Text) -end -self:T(self.lid..string.format("Radio transmission on %.3f MHz %s: %s",freq,UTILS.GetModulationName(modu),Text)) -self.msrs:PlayText(Text) -end -end -return self -end -function OPSGROUP:SetCarrierLoaderAllAspect(Length,Width) -self.carrierLoader.type="front" -self.carrierLoader.length=Length or 50 -self.carrierLoader.width=Width or 20 -return self -end -function OPSGROUP:SetCarrierLoaderFront(Length,Width) -self.carrierLoader.type="front" -self.carrierLoader.length=Length or 50 -self.carrierLoader.width=Width or 20 -return self -end -function OPSGROUP:SetCarrierLoaderBack(Length,Width) -self.carrierLoader.type="back" -self.carrierLoader.length=Length or 50 -self.carrierLoader.width=Width or 20 -return self -end -function OPSGROUP:SetCarrierLoaderStarboard(Length,Width) -self.carrierLoader.type="right" -self.carrierLoader.length=Length or 50 -self.carrierLoader.width=Width or 20 -return self -end -function OPSGROUP:SetCarrierLoaderPort(Length,Width) -self.carrierLoader.type="left" -self.carrierLoader.length=Length or 50 -self.carrierLoader.width=Width or 20 -return self -end -function OPSGROUP:SetCarrierUnloaderAllAspect(Length,Width) -self.carrierUnloader.type="front" -self.carrierUnloader.length=Length or 50 -self.carrierUnloader.width=Width or 20 -return self -end -function OPSGROUP:SetCarrierUnloaderFront(Length,Width) -self.carrierUnloader.type="front" -self.carrierUnloader.length=Length or 50 -self.carrierUnloader.width=Width or 20 -return self -end -function OPSGROUP:SetCarrierUnloaderBack(Length,Width) -self.carrierUnloader.type="back" -self.carrierUnloader.length=Length or 50 -self.carrierUnloader.width=Width or 20 -return self -end -function OPSGROUP:SetCarrierUnloaderStarboard(Length,Width) -self.carrierUnloader.type="right" -self.carrierUnloader.length=Length or 50 -self.carrierUnloader.width=Width or 20 -return self -end -function OPSGROUP:SetCarrierUnloaderPort(Length,Width) -self.carrierUnloader.type="left" -self.carrierUnloader.length=Length or 50 -self.carrierUnloader.width=Width or 20 -return self -end -function OPSGROUP:IsInZone(Zone) -local vec2=self:GetVec2() -local is=false -if vec2 then -is=Zone:IsVec2InZone(vec2) -else -self:T3(self.lid.."WARNING: Cannot get vec2 at IsInZone()!") -end -return is -end -function OPSGROUP:Get2DDistance(Coordinate) -local a=self:GetVec2() -local b={} -if Coordinate.z then -b.x=Coordinate.x -b.y=Coordinate.z -else -b.x=Coordinate.x -b.y=Coordinate.y -end -local dist=UTILS.VecDist2D(a,b) -return dist -end -function OPSGROUP:IsFlightgroup() -return self.isFlightgroup -end -function OPSGROUP:IsArmygroup() -return self.isArmygroup -end -function OPSGROUP:IsNavygroup() -return self.isNavygroup -end -function OPSGROUP:IsExist() -local DCSGroup=self:GetDCSGroup() -if DCSGroup then -local exists=DCSGroup:isExist() -return exists -end -return nil -end -function OPSGROUP:IsActive() -if self.group then -local active=self.group:IsActive() -return active -end -return nil -end -function OPSGROUP:IsAlive() -if self.group then -local alive=self.group:IsAlive() -return alive -end -return nil -end -function OPSGROUP:IsLateActivated() -return self.isLateActivated -end -function OPSGROUP:IsInUtero() -local is=self:Is("InUtero")and not self:IsDead() -return is -end -function OPSGROUP:IsSpawned() -local is=self:Is("Spawned") -return is -end -function OPSGROUP:IsDead() -return self.isDead -end -function OPSGROUP:IsDestroyed() -return self.isDestroyed -end -function OPSGROUP:IsStopped() -local is=self:Is("Stopped") -return is -end -function OPSGROUP:IsUncontrolled() -return self.isUncontrolled -end -function OPSGROUP:HasPassedFinalWaypoint() -return self.passedfinalwp -end -function OPSGROUP:IsRearming() -local rearming=self:Is("Rearming")or self:Is("Rearm") -return rearming -end -function OPSGROUP:IsOutOfAmmo() -return self.outofAmmo -end -function OPSGROUP:IsOutOfBombs() -return self.outofBombs -end -function OPSGROUP:IsOutOfGuns() -return self.outofGuns -end -function OPSGROUP:IsOutOfMissiles() -return self.outofMissiles -end -function OPSGROUP:IsOutOfTorpedos() -return self.outofTorpedos -end -function OPSGROUP:IsLasing() -return self.spot.On -end -function OPSGROUP:IsRetreating() -local is=self:is("Retreating")or self:is("Retreated") -return is -end -function OPSGROUP:IsRetreated() -local is=self:is("Retreated") -return is -end -function OPSGROUP:IsReturning() -local is=self:is("Returning") -return is -end -function OPSGROUP:IsEngaging() -local is=self:is("Engaging") -return is -end -function OPSGROUP:IsWaiting() -if self.Twaiting then -return true -end -return false -end -function OPSGROUP:IsNotCarrier() -return self.carrierStatus==OPSGROUP.CarrierStatus.NOTCARRIER -end -function OPSGROUP:IsCarrier() -return not self:IsNotCarrier() -end -function OPSGROUP:IsPickingup() -return self.carrierStatus==OPSGROUP.CarrierStatus.PICKUP -end -function OPSGROUP:IsLoading() -return self.carrierStatus==OPSGROUP.CarrierStatus.LOADING -end -function OPSGROUP:IsTransporting() -return self.carrierStatus==OPSGROUP.CarrierStatus.TRANSPORTING -end -function OPSGROUP:IsUnloading() -return self.carrierStatus==OPSGROUP.CarrierStatus.UNLOADING -end -function OPSGROUP:IsCargo(CheckTransport) -return not self:IsNotCargo(CheckTransport) -end -function OPSGROUP:IsNotCargo(CheckTransport) -local notcargo=self.cargoStatus==OPSGROUP.CargoStatus.NOTCARGO -if notcargo then -return true -else -if CheckTransport then -if self.cargoTransportUID==nil then -return true -else -return false -end -else -return false -end -end -return notcargo -end -function OPSGROUP:_AddMyLift(Transport) -self.mylifts=self.mylifts or{} -self.mylifts[Transport.uid]=true -return self -end -function OPSGROUP:_DelMyLift(Transport) -if self.mylifts then -self.mylifts[Transport.uid]=nil -end -return self -end -function OPSGROUP:IsAwaitingLift(Transport) -if self.mylifts then -for uid,iswaiting in pairs(self.mylifts)do -if Transport==nil or Transport.uid==uid then -if iswaiting==true then -return true -end -end -end -end -return false -end -function OPSGROUP:_GetPausedMission() -if self.pausedmissions and#self.pausedmissions>0 then -for _,mid in pairs(self.pausedmissions)do -if mid then -local mission=self:GetMissionByID(mid) -if mission and mission:IsNotOver()then -return mission -end -end -end -end -return nil -end -function OPSGROUP:_CountPausedMissions() -local N=0 -if self.pausedmissions and#self.pausedmissions>0 then -for _,mid in pairs(self.pausedmissions)do -local mission=self:GetMissionByID(mid) -if mission and mission:IsNotOver()then -N=N+1 -end -end -end -return N -end -function OPSGROUP:_RemovePausedMission(AuftragsNummer) -if self.pausedmissions and#self.pausedmissions>0 then -for i=#self.pausedmissions,1,-1 do -local mid=self.pausedmissions[i] -if mid==AuftragsNummer then -table.remove(self.pausedmissions,i) -return self -end -end -end -return self -end -function OPSGROUP:IsBoarding(CarrierGroupName) -if CarrierGroupName then -local carrierGroup=self:_GetMyCarrierGroup() -if carrierGroup and carrierGroup.groupname~=CarrierGroupName then -return false -end -end -return self.cargoStatus==OPSGROUP.CargoStatus.BOARDING -end -function OPSGROUP:IsLoaded(CarrierGroupName) -local isloaded=self.cargoStatus==OPSGROUP.CargoStatus.LOADED -if not isloaded then -return false -end -if CarrierGroupName then -if type(CarrierGroupName)~="table"then -CarrierGroupName={CarrierGroupName} -end -for _,CarrierName in pairs(CarrierGroupName)do -local carrierGroup=self:_GetMyCarrierGroup() -if carrierGroup and carrierGroup.groupname==CarrierName then -return isloaded -end -end -return false -end -return isloaded -end -function OPSGROUP:IsBusy() -if self:IsBoarding()then -return true -end -if self:IsRearming()then -return true -end -if self:IsReturning()then -return true -end -if self:IsPickingup()or self:IsLoading()or self:IsTransporting()or self:IsUnloading()then -return true -end -if self:IsEngaging()then -return true -end -return false -end -function OPSGROUP:GetWaypoints() -return self.waypoints -end -function OPSGROUP:MarkWaypoints(Duration) -for i,_waypoint in pairs(self.waypoints or{})do -local waypoint=_waypoint -local text=string.format("Waypoint ID=%d of %s",waypoint.uid,self.groupname) -text=text..string.format("\nSpeed=%.1f kts, Alt=%d ft (%s)",UTILS.MpsToKnots(waypoint.speed),UTILS.MetersToFeet(waypoint.alt or 0),"BARO") -if waypoint.marker then -if waypoint.marker.text~=text then -waypoint.marker.text=text -end -else -waypoint.marker=MARKER:New(waypoint.coordinate,text):ToCoalition(self:GetCoalition()) -end -end -if Duration then -self:RemoveWaypointMarkers(Duration) -end -return self -end -function OPSGROUP:RemoveWaypointMarkers(Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,OPSGROUP.RemoveWaypointMarkers,self) -else -for i,_waypoint in pairs(self.waypoints or{})do -local waypoint=_waypoint -if waypoint.marker then -waypoint.marker:Remove() -end -end -end -return self -end -function OPSGROUP:GetWaypointByID(uid) -for _,_waypoint in pairs(self.waypoints or{})do -local waypoint=_waypoint -if waypoint.uid==uid then -return waypoint -end -end -return nil -end -function OPSGROUP:GetWaypointByIndex(index) -for i,_waypoint in pairs(self.waypoints)do -local waypoint=_waypoint -if i==index then -return waypoint -end -end -return nil -end -function OPSGROUP:GetWaypointUIDFromIndex(index) -for i,_waypoint in pairs(self.waypoints)do -local waypoint=_waypoint -if i==index then -return waypoint.uid -end -end -return nil -end -function OPSGROUP:GetWaypointIndex(uid) -if uid then -for i,_waypoint in pairs(self.waypoints or{})do -local waypoint=_waypoint -if waypoint.uid==uid then -return i -end -end -end -return nil -end -function OPSGROUP:GetWaypointIndexNext(cyclic,i) -if cyclic==nil then -cyclic=self.adinfinitum -end -local N=#self.waypoints -i=i or self.currentwp -local n=math.min(i+1,N) -if cyclic and i==N then -n=1 -end -return n -end -function OPSGROUP:GetWaypointIndexCurrent() -return self.currentwp or 1 -end -function OPSGROUP:GetWaypointIndexAfterID(uid) -local index=self:GetWaypointIndex(uid) -if index then -return index+1 -else -return#self.waypoints+1 -end -end -function OPSGROUP:GetWaypoint(indx) -return self.waypoints[indx] -end -function OPSGROUP:GetWaypointFinal() -return self.waypoints[#self.waypoints] -end -function OPSGROUP:GetWaypointNext(cyclic) -local n=self:GetWaypointIndexNext(cyclic) -return self.waypoints[n] -end -function OPSGROUP:GetWaypointCurrent() -return self.waypoints[self.currentwp] -end -function OPSGROUP:GetWaypointCurrentUID() -local wp=self:GetWaypointCurrent() -if wp then -return wp.uid -end -return nil -end -function OPSGROUP:GetNextWaypointCoordinate(cyclic) -local waypoint=self:GetWaypointNext(cyclic) -return waypoint.coordinate -end -function OPSGROUP:GetWaypointCoordinate(index) -local waypoint=self:GetWaypoint(index) -if waypoint then -return waypoint.coordinate -end -return nil -end -function OPSGROUP:GetWaypointSpeed(indx) -local waypoint=self:GetWaypoint(indx) -if waypoint then -return UTILS.MpsToKnots(waypoint.speed) -end -return nil -end -function OPSGROUP:GetWaypointUID(waypoint) -return waypoint.uid -end -function OPSGROUP:GetWaypointID(indx) -local waypoint=self:GetWaypoint(indx) -if waypoint then -return waypoint.uid -end -return nil -end -function OPSGROUP:GetSpeedToWaypoint(indx) -local speed=self:GetWaypointSpeed(indx) -if speed<=0.01 then -speed=self:GetSpeedCruise() -end -return speed -end -function OPSGROUP:GetDistanceToWaypoint(indx) -local dist=0 -if#self.waypoints>0 then -indx=indx or self:GetWaypointIndexNext() -local wp=self:GetWaypoint(indx) -if wp then -local coord=self:GetCoordinate() -dist=coord:Get2DDistance(wp.coordinate) -end -end -return dist -end -function OPSGROUP:GetTimeToWaypoint(indx) -local s=self:GetDistanceToWaypoint(indx) -local v=self:GetVelocity() -local t=s/v -if t==math.inf then -return 365*24*60*60 -elseif t==math.nan then -return 0 -else -return t -end -end -function OPSGROUP:GetExpectedSpeed() -if self:IsHolding()or self:Is("Rearming")or self:IsWaiting()or self:IsRetreated()then -return 0 -else -return self.speedWp or 0 -end -end -function OPSGROUP:RemoveWaypointByID(uid) -local index=self:GetWaypointIndex(uid) -if index then -self:RemoveWaypoint(index) -end -return self -end -function OPSGROUP:RemoveWaypoint(wpindex) -if self.waypoints then -local wp=self:GetWaypoint(wpindex) -local istemp=wp.temp or wp.detour or wp.astar or wp.missionUID -local N=#self.waypoints -if N==1 then -self:T(self.lid..string.format("ERROR: Cannot remove waypoint with index=%d! It is the only waypoint and a group needs at least ONE waypoint",wpindex)) -return self -end -if wpindex>N then -self:T(self.lid..string.format("ERROR: Cannot remove waypoint with index=%d as there are only N=%d waypoints!",wpindex,N)) -return self -end -if wp and wp.marker then -wp.marker:Remove() -end -table.remove(self.waypoints,wpindex) -local n=#self.waypoints -self:T(self.lid..string.format("Removing waypoint UID=%d [temp=%s]: index=%d [currentwp=%d]. N %d-->%d",wp.uid,tostring(istemp),wpindex,self.currentwp,N,n)) -if wpindex>self.currentwp then -if self.currentwp>=n and not(self.adinfinitum or istemp)then -self:_PassedFinalWaypoint(true,"Removed FUTURE waypoint we are currently moving to and that was the LAST waypoint") -end -self:_CheckGroupDone(1) -else -if self.currentwp==1 then -if self.adinfinitum then -self.currentwp=#self.waypoints -else -self.currentwp=1 -end -else -self.currentwp=self.currentwp-1 -end -if(self.adinfinitum or istemp)then -self:_PassedFinalWaypoint(false,"Removed PASSED temporary waypoint") -end -end -end -return self -end -function OPSGROUP:OnEventBirth(EventData) -if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then -local unit=EventData.IniUnit -local group=EventData.IniGroup -local unitname=EventData.IniUnitName -if self.isFlightgroup then -if EventData.Place then -self.homebase=self.homebase or EventData.Place -self.currbase=EventData.Place -else -self.currbase=nil -end -if self.homebase and not self.destbase then -self.destbase=self.homebase -end -self:T(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned",unitname,self.currbase and self.currbase:GetName()or"unknown")) -else -self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned",unitname)) -end -local element=self:GetElementByName(unitname) -if element and element.status~=OPSGROUP.ElementStatus.SPAWNED then -self:T(self.lid..string.format("EVENT: Element %s born ==> spawned",unitname)) -self:ElementSpawned(element) -end -end -end -function OPSGROUP:OnEventHit(EventData) -if EventData and EventData.TgtGroup and EventData.TgtUnit and EventData.TgtGroupName and EventData.TgtGroupName==self.groupname then -self:T2(self.lid..string.format("EVENT: Unit %s hit!",EventData.TgtUnitName)) -local unit=EventData.TgtUnit -local group=EventData.TgtGroup -local unitname=EventData.TgtUnitName -local element=self:GetElementByName(unitname) -self.Nhit=self.Nhit or 0 -self.Nhit=self.Nhit+1 -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -self:ElementHit(element,EventData.IniUnit) -end -end -end -function OPSGROUP:OnEventDead(EventData) -if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then -self:T2(self.lid..string.format("EVENT: Unit %s dead!",EventData.IniUnitName)) -local unit=EventData.IniUnit -local group=EventData.IniGroup -local unitname=EventData.IniUnitName -local element=self:GetElementByName(unitname) -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed",element.name)) -self:ElementDestroyed(element) -end -end -end -function OPSGROUP:OnEventRemoveUnit(EventData) -if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then -self:T2(self.lid..string.format("EVENT: Unit %s removed!",EventData.IniUnitName)) -local unit=EventData.IniUnit -local group=EventData.IniGroup -local unitname=EventData.IniUnitName -local element=self:GetElementByName(unitname) -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -self:T(self.lid..string.format("EVENT: Element %s removed ==> dead",element.name)) -self:ElementDead(element) -end -end -end -function OPSGROUP:OnEventPlayerLeaveUnit(EventData) -if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then -self:T2(self.lid..string.format("EVENT: Player left Unit %s!",EventData.IniUnitName)) -local unit=EventData.IniUnit -local group=EventData.IniGroup -local unitname=EventData.IniUnitName -local element=self:GetElementByName(unitname) -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -self:T(self.lid..string.format("EVENT: Player left Element %s ==> dead",element.name)) -self:ElementDead(element) -end -end -end -function OPSGROUP:OnEventKill(EventData) -if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then -local targetname=tostring(EventData.TgtUnitName) -self:T2(self.lid..string.format("EVENT: Unit %s killed object %s!",tostring(EventData.IniUnitName),targetname)) -local target=UNIT:FindByName(targetname) -if not target then -target=STATIC:FindByName(targetname,false) -end -if target then -self:T(self.lid..string.format("EVENT: Unit %s killed unit/static %s!",tostring(EventData.IniUnitName),targetname)) -self.Nkills=self.Nkills+1 -local mission=self:GetMissionCurrent() -if mission then -mission.Nkills=mission.Nkills+1 -end -end -end -end -function OPSGROUP:SetTask(DCSTask) -if self:IsAlive()then -if self.taskenroute and#self.taskenroute>0 then -if tostring(DCSTask.id)=="ComboTask"then -for _,task in pairs(self.taskenroute)do -table.insert(DCSTask.params.tasks,1,task) -end -else -local tasks=UTILS.DeepCopy(self.taskenroute) -table.insert(tasks,DCSTask) -DCSTask=self.group.TaskCombo(self,tasks) -end -end -self.controller:setTask(DCSTask) -local text=string.format("SETTING Task %s",tostring(DCSTask.id)) -if tostring(DCSTask.id)=="ComboTask"then -for i,task in pairs(DCSTask.params.tasks)do -text=text..string.format("\n[%d] %s",i,tostring(task.id)) -end -end -self:T(self.lid..text) -end -return self -end -function OPSGROUP:PushTask(DCSTask) -if self:IsAlive()then -if self.taskenroute and#self.taskenroute>0 then -if tostring(DCSTask.id)=="ComboTask"then -for _,task in pairs(self.taskenroute)do -table.insert(DCSTask.params.tasks,1,task) -end -else -local tasks=UTILS.DeepCopy(self.taskenroute) -table.insert(tasks,DCSTask) -DCSTask=self.group.TaskCombo(self,tasks) -end -end -self.controller:pushTask(DCSTask) -local text=string.format("PUSHING Task %s",tostring(DCSTask.id)) -if tostring(DCSTask.id)=="ComboTask"then -for i,task in pairs(DCSTask.params.tasks)do -text=text..string.format("\n[%d] %s",i,tostring(task.id)) -end -end -self:T(self.lid..text) -end -return self -end -function OPSGROUP:HasTaskController() -local hastask=nil -if self.controller then -hastask=self.controller:hasTask() -end -self:T3(self.lid..string.format("Controller hasTask=%s",tostring(hastask))) -return hastask -end -function OPSGROUP:ClearTasks() -local hastask=self:HasTaskController() -if self:IsAlive()and self.controller and self:HasTaskController()then -self:T(self.lid..string.format("CLEARING Tasks")) -self.controller:resetTask() -end -return self -end -function OPSGROUP:AddTask(task,clock,description,prio,duration) -local newtask=self:NewTaskScheduled(task,clock,description,prio,duration) -table.insert(self.taskqueue,newtask) -self:T(self.lid..string.format("Adding SCHEDULED task %s starting at %s",newtask.description,UTILS.SecondsToClock(newtask.time,true))) -self:T3({newtask=newtask}) -return newtask -end -function OPSGROUP:NewTaskScheduled(task,clock,description,prio,duration) -self.taskcounter=self.taskcounter+1 -local time=timer.getAbsTime()+5 -if clock then -if type(clock)=="string"then -time=UTILS.ClockToSeconds(clock) -elseif type(clock)=="number"then -time=timer.getAbsTime()+clock -end -end -local newtask={} -newtask.status=OPSGROUP.TaskStatus.SCHEDULED -newtask.dcstask=task -newtask.description=description or task.id -newtask.prio=prio or 50 -newtask.time=time -newtask.id=self.taskcounter -newtask.duration=duration -newtask.waypoint=-1 -newtask.type=OPSGROUP.TaskType.SCHEDULED -newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d",self.groupname,newtask.id)) -newtask.stopflag:Set(0) -return newtask -end -function OPSGROUP:AddTaskWaypoint(task,Waypoint,description,prio,duration) -Waypoint=Waypoint or self:GetWaypointNext() -if Waypoint then -self.taskcounter=self.taskcounter+1 -local newtask={} -newtask.description=description or string.format("Task #%d",self.taskcounter) -newtask.status=OPSGROUP.TaskStatus.SCHEDULED -newtask.dcstask=task -newtask.prio=prio or 50 -newtask.id=self.taskcounter -newtask.duration=duration -newtask.time=0 -newtask.waypoint=Waypoint.uid -newtask.type=OPSGROUP.TaskType.WAYPOINT -newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d",self.groupname,newtask.id)) -newtask.stopflag:Set(0) -table.insert(self.taskqueue,newtask) -self:T(self.lid..string.format("Adding WAYPOINT task %s at WP ID=%d",newtask.description,newtask.waypoint)) -self:T3({newtask=newtask}) -return newtask -end -return nil -end -function OPSGROUP:AddTaskEnroute(task) -if not self.taskenroute then -self.taskenroute={} -end -local gotit=false -for _,Task in pairs(self.taskenroute)do -if Task.id==task.id then -gotit=true -break -end -end -if not gotit then -self:T(self.lid..string.format("Adding enroute task")) -table.insert(self.taskenroute,task) -end -end -function OPSGROUP:GetTasksWaypoint(id) -local tasks={} -self:_SortTaskQueue() -for _,_task in pairs(self.taskqueue)do -local task=_task -if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED and task.waypoint==id then -table.insert(tasks,task) -end -end -return tasks -end -function OPSGROUP:CountTasksWaypoint(id) -local n=0 -for _,_task in pairs(self.taskqueue)do -local task=_task -if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED and task.waypoint==id then -n=n+1 -end -end -return n -end -function OPSGROUP:_SortTaskQueue() -local function _sort(a,b) -local taskA=a -local taskB=b -return(taskA.prio=task.time then -return task -end -end -return nil -end -function OPSGROUP:GetTaskCurrent() -local task=self:GetTaskByID(self.taskcurrent,OPSGROUP.TaskStatus.EXECUTING) -return task -end -function OPSGROUP:GetTaskByID(id,status) -for _,_task in pairs(self.taskqueue)do -local task=_task -if task.id==id then -if status==nil or status==task.status then -return task -end -end -end -return nil -end -function OPSGROUP:onbeforeTaskExecute(From,Event,To,Task) -local Mission=self:GetMissionByTaskID(Task.id) -if Mission and(Mission.Tpush or#Mission.conditionPush>0)then -if Mission:IsReadyToPush()then -if self:IsWaiting()then -self.Twaiting=nil -self.dTwait=nil -if self:IsFlightgroup()then -self.flaghold:Set(1) -end -end -else -if self:IsWaiting()then -else -local alt=Mission.missionAltitude and UTILS.MetersToFeet(Mission.missionAltitude)or nil -self:Wait(nil,alt) -end -local dt=Mission.Tpush and Mission.Tpush-timer.getAbsTime()or 20 -self:T(self.lid..string.format("Mission %s task execute suspended for %d seconds",Mission.name,dt)) -self:__TaskExecute(-dt,Task) -return false -end -end -if Mission and Mission.opstransport then -local delivered=Mission.opstransport:IsCargoDelivered(self.groupname) -if not delivered then -local dt=30 -self:T(self.lid..string.format("Mission %s task execute suspended for %d seconds because we were not delivered",Mission.name,dt)) -self:__TaskExecute(-dt,Task) -if(self:IsArmygroup()or self:IsNavygroup())and self:IsCruising()then -self:FullStop() -end -return false -end -end -return true -end -function OPSGROUP:onafterTaskExecute(From,Event,To,Task) -local text=string.format("Task %s ID=%d execute",tostring(Task.description),Task.id) -self:T(self.lid..text) -self:T2({Task}) -if self.taskcurrent>0 then -self:TaskCancel() -end -self.taskcurrent=Task.id -Task.timestamp=timer.getAbsTime() -Task.status=OPSGROUP.TaskStatus.EXECUTING -if self:GetTaskCurrent()==nil then -table.insert(self.taskqueue,Task) -end -local Mission=self:GetMissionByTaskID(self.taskcurrent) -self:_UpdateTask(Task,Mission) -if Mission then -self:MissionExecute(Mission) -end -end -function OPSGROUP:_UpdateTask(Task,Mission) -Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) -if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then -local followSet=SET_GROUP:New():AddGroup(self.group) -local param=Task.dcstask.params -local followUnit=UNIT:FindByName(param.unitname) -Task.formation=AI_FORMATION:New(followUnit,followSet,AUFTRAG.SpecialTask.FORMATION,"Follow X at given parameters.") -Task.formation:FormationCenterWing(-param.offsetX,50,math.abs(param.altitude),50,param.offsetZ,50) -Task.formation:SetFollowTimeInterval(param.dtFollow) -Task.formation:SetFlightModeFormation(self.group) -Task.formation:Start() -elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then -local zone=Task.dcstask.params.zone -local surfacetypes=nil -if self:IsArmygroup()then -surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD} -elseif self:IsNavygroup()then -surfacetypes={land.SurfaceType.WATER,land.SurfaceType.SHALLOW_WATER} -end -local Coordinate=zone:GetRandomCoordinate(nil,nil,surfacetypes) -local Speed=Task.dcstask.params.speed and UTILS.MpsToKnots(Task.dcstask.params.speed)or UTILS.KmphToKnots(self.speedCruise) -local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude)or nil -local currUID=self:GetWaypointCurrent().uid -local wp=nil -if self.isFlightgroup then -wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) -elseif self.isArmygroup then -wp=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Task.dcstask.params.formation) -elseif self.isNavygroup then -wp=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) -end -wp.missionUID=Mission and Mission.auftragsnummer or nil -elseif Task.dcstask.id==AUFTRAG.SpecialTask.RECON then -local target=Task.dcstask.params.target -self.reconindecies={} -for i=1,#target.targets do -table.insert(self.reconindecies,i) -end -local n=1 -if Task.dcstask.params.randomly then -n=UTILS.GetRandomTableElement(self.reconindecies) -else -table.remove(self.reconindecies,n) -end -local object=target.targets[n] -local zone=object.Object -local Coordinate=zone:GetRandomCoordinate() -local Speed=Task.dcstask.params.speed and UTILS.MpsToKnots(Task.dcstask.params.speed)or UTILS.KmphToKnots(self.speedCruise) -local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude)or nil -local currUID=self:GetWaypointCurrent().uid -local wp=nil -if self.isFlightgroup then -wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) -elseif self.isArmygroup then -wp=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Task.dcstask.params.formation) -elseif self.isNavygroup then -wp=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) -end -wp.missionUID=Mission and Mission.auftragsnummer or nil -elseif Task.dcstask.id==AUFTRAG.SpecialTask.AMMOSUPPLY or Task.dcstask.id==AUFTRAG.SpecialTask.FUELSUPPLY then -elseif Task.dcstask.id==AUFTRAG.SpecialTask.REARMING then -local rearmed=self:_CheckAmmoFull() -if rearmed then -self:T2(self.lid.."Ammo already full ==> reaming task done!") -self:TaskDone(Task) -else -self:T2(self.lid.."Ammo not full ==> Rearm()") -self:Rearm() -end -elseif Task.dcstask.id==AUFTRAG.SpecialTask.ALERT5 then -elseif Task.dcstask.id==AUFTRAG.SpecialTask.ONGUARD or Task.dcstask.id==AUFTRAG.SpecialTask.ARMOREDGUARD then -if self:IsArmygroup()or self:IsNavygroup()then -self:FullStop() -else -end -elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then -if self:IsArmygroup()or self:IsNavygroup()then -self:__FullStop(0.1) -else -end -elseif Task.dcstask.id==AUFTRAG.SpecialTask.AIRDEFENSE or Task.dcstask.id==AUFTRAG.SpecialTask.EWR then -if self:IsArmygroup()or self:IsNavygroup()then -self:FullStop() -else -end -elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK then -local target=Task.dcstask.params.target -local speed=self.speedMax and UTILS.KmphToKnots(self.speedMax)or nil -if Task.dcstask.params.speed then -speed=Task.dcstask.params.speed -end -if target then -self:EngageTarget(target,speed,Task.dcstask.params.formation) -end -elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then -if self.isFlightgroup then -self:T("We are Special Auftrag Patrol Race Track, starting now ...") -local aircraft=self:GetGroup() -aircraft:PatrolRaceTrack(Task.dcstask.params.TrackPoint1,Task.dcstask.params.TrackPoint2,Task.dcstask.params.TrackAltitude,Task.dcstask.params.TrackSpeed,Task.dcstask.params.TrackFormation,false,1) -end -elseif Task.dcstask.id==AUFTRAG.SpecialTask.HOVER then -if self.isFlightgroup then -self:T("We are Special Auftrag HOVER, hovering now ...") -local alt=Task.dcstask.params.hoverAltitude -local time=Task.dcstask.params.hoverTime -local mSpeed=Task.dcstask.params.missionSpeed or self.speedCruise or 150 -local Speed=UTILS.KmphToKnots(mSpeed) -local CruiseAlt=UTILS.FeetToMeters(Task.dcstask.params.missionAltitude or 1000) -local helo=self:GetGroup() -helo:SetSpeed(0.01,true) -helo:SetAltitude(alt,true,"BARO") -self:HoverStart() -local function FlyOn(Helo,Speed,CruiseAlt,Task) -if Helo then -Helo:SetSpeed(Speed,true) -Helo:SetAltitude(CruiseAlt,true,"BARO") -self:T("We are Special Auftrag HOVER, end of hovering now ...") -self:TaskDone(Task) -self:HoverEnd() -end -end -local timer=TIMER:New(FlyOn,helo,Speed,CruiseAlt,Task) -timer:Start(time) -end -elseif Task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then -self:T(self.lid.."Executing task for relocation mission") -local legion=Task.dcstask.params.legion -local Coordinate=legion.spawnzone:GetRandomCoordinate() -local currUID=self:GetWaypointCurrent().uid -local wp=nil -if self.isArmygroup then -self:T2(self.lid.."Routing group to spawn zone of new legion") -wp=ARMYGROUP.AddWaypoint(self,Coordinate,UTILS.KmphToKnots(self.speedCruise),currUID,Mission.optionFormation) -elseif self.isFlightgroup then -self:T2(self.lid.."Routing group to intermediate point near new legion") -Coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate,0.8) -wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,UTILS.KmphToKnots(self.speedCruise),currUID,UTILS.MetersToFeet(self.altitudeCruise)) -elseif self.isNavygroup then -self:T2(self.lid.."Routing group to spawn zone of new legion") -wp=NAVYGROUP.AddWaypoint(self,Coordinate,UTILS.KmphToKnots(self.speedCruise),currUID) -else -end -wp.missionUID=Mission and Mission.auftragsnummer or nil -elseif Task.dcstask.id==AUFTRAG.SpecialTask.CAPTUREZONE then -if self:IsEngaging()then -self:T2(self.lid..string.format("CaptureZone: Engaging currently!")) -else -local Coalitions=UTILS.GetCoalitionEnemy(self:GetCoalition(),false) -local zoneCurr=Task.target -if zoneCurr then -self:T(self.lid..string.format("Current target zone=%s owner=%s",zoneCurr:GetName(),zoneCurr:GetOwnerName())) -if zoneCurr:GetOwner()==self:GetCoalition()then -self:T(self.lid..string.format("Zone %s captured ==> Task DONE!",zoneCurr:GetName())) -self:TaskDone(Task) -else -self:T(self.lid..string.format("Zone %s NOT captured!",zoneCurr:GetName())) -if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then -self:T(self.lid..string.format("Zone %s NOT captured and EXECUTING ==> Find target",zoneCurr:GetName())) -local targetgroup=zoneCurr:GetScannedGroupSet():GetClosestGroup(self.coordinate,Coalitions) -if targetgroup then -self:T(self.lid..string.format("Zone %s NOT captured: engaging target %s",zoneCurr:GetName(),targetgroup:GetName())) -self:EngageTarget(targetgroup) -else -if self:IsFlightgroup()then -self:T(self.lid..string.format("Zone %s not captured but no target group could be found ==> TaskDone as FLIGHTGROUPS cannot capture zones",zoneCurr:GetName())) -self:TaskDone(Task) -else -self:T(self.lid..string.format("Zone %s not captured but no target group could be found. Should be captured in the next zone evaluation.",zoneCurr:GetName())) -end -end -else -self:T(self.lid..string.format("Zone %s NOT captured and NOT EXECUTING",zoneCurr:GetName())) -end -end -else -self:T(self.lid..string.format("NO Current target zone=%s")) -end -end -else -if Task.type==OPSGROUP.TaskType.SCHEDULED or Task.ismission then -local DCSTask=nil -if Task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then -local vec2=self:GetVec2() -local param=Task.dcstask.params -local heading=param.heading or math.random(1,360) -local Altitude=param.altitude or 500 -local Alpha=param.angle or math.random(45,85) -local distance=Altitude/math.tan(math.rad(Alpha)) -local tvec2=UTILS.Vec2Translate(vec2,distance,heading) -self:T(self.lid..string.format("Barrage: Shots=%s, Altitude=%d m, Angle=%d°, heading=%03d°, distance=%d m",tostring(param.shots),Altitude,Alpha,heading,distance)) -DCSTask=CONTROLLABLE.TaskFireAtPoint(nil,tvec2,param.radius,param.shots,param.weaponType,Altitude) -elseif Task.ismission and Task.dcstask.id=='FireAtPoint'then -DCSTask=UTILS.DeepCopy(Task.dcstask) -local ammo=self:GetAmmoTot() -local nAmmo=ammo.Total -local weaponType=DCSTask.params.weaponType or-1 -if weaponType==ENUMS.WeaponFlag.CruiseMissile then -nAmmo=ammo.MissilesCR -elseif weaponType==ENUMS.WeaponFlag.AnyRocket then -nAmmo=ammo.Rockets -elseif weaponType==ENUMS.WeaponFlag.Cannons then -nAmmo=ammo.Guns -end -local nShots=DCSTask.params.expendQty or 1 -self:T(self.lid..string.format("Fire at point with nshots=%d of %d",nShots,nAmmo)) -if nShots==-1 then -nShots=nAmmo -self:T(self.lid..string.format("Fire at point taking max amount of ammo = %d",nShots)) -elseif nShots<1 then -local p=nShots -nShots=UTILS.Round(p*nAmmo,0) -self:T(self.lid..string.format("Fire at point taking %.1f percent amount of ammo = %d",p,nShots)) -else -nShots=math.min(nShots,nAmmo) -end -DCSTask.params.expendQty=nShots -else -DCSTask=Task.dcstask -end -self:_SandwitchDCSTask(DCSTask,Task) -elseif Task.type==OPSGROUP.TaskType.WAYPOINT then -else -self:T(self.lid.."ERROR: Unknown task type: ") -end -end -end -function OPSGROUP:_SandwitchDCSTask(DCSTask,Task,SetTask,Delay) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,OPSGROUP._SandwitchDCSTask,self,DCSTask,Task,SetTask) -else -local DCStasks={} -if DCSTask.id=='ComboTask'then -for TaskID,Task in ipairs(DCSTask.params.tasks)do -table.insert(DCStasks,Task) -end -else -table.insert(DCStasks,DCSTask) -end -local TaskCombo=self.group:TaskCombo(DCStasks) -local TaskCondition=self.group:TaskCondition(nil,Task.stopflag:GetName(),1,nil,Task.duration) -local TaskControlled=self.group:TaskControlled(TaskCombo,TaskCondition) -local TaskDone=self.group:TaskFunction("OPSGROUP._TaskDone",self,Task) -local TaskFinal=self.group:TaskCombo({TaskControlled,TaskDone}) -if SetTask then -self:SetTask(TaskFinal) -else -self:PushTask(TaskFinal) -end -end -end -function OPSGROUP:onafterTaskCancel(From,Event,To,Task) -local currenttask=self:GetTaskCurrent() -Task=Task or currenttask -if Task then -if currenttask and Task.id==currenttask.id then -local stopflag=Task.stopflag:Get() -local text=string.format("Current task %s ID=%d cancelled (flag %s=%d)",Task.description,Task.id,Task.stopflag:GetName(),stopflag) -self:T(self.lid..text) -Task.stopflag:Set(1) -local done=false -if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then -Task.formation:Stop() -done=true -elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then -done=true -elseif Task.dcstask.id==AUFTRAG.SpecialTask.RECON then -done=true -elseif Task.dcstask.id==AUFTRAG.SpecialTask.AMMOSUPPLY then -done=true -elseif Task.dcstask.id==AUFTRAG.SpecialTask.FUELSUPPLY then -done=true -elseif Task.dcstask.id==AUFTRAG.SpecialTask.REARMING then -done=true -elseif Task.dcstask.id==AUFTRAG.SpecialTask.ALERT5 then -done=true -elseif Task.dcstask.id==AUFTRAG.SpecialTask.ONGUARD or Task.dcstask.id==AUFTRAG.SpecialTask.ARMOREDGUARD then -done=true -elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK then -done=true -elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then -done=true -elseif stopflag==1 or(not self:IsAlive())or self:IsDead()or self:IsStopped()then -done=true -end -if done then -self:TaskDone(Task) -end -else -self:T(self.lid..string.format("TaskCancel: Setting task %s ID=%d to DONE",Task.description,Task.id)) -self:TaskDone(Task) -end -else -local text=string.format("WARNING: No (current) task to cancel!") -self:T(self.lid..text) -end -end -function OPSGROUP:onbeforeTaskDone(From,Event,To,Task) -local allowed=true -if Task.status==OPSGROUP.TaskStatus.PAUSED then -allowed=false -end -return allowed -end -function OPSGROUP:onafterTaskDone(From,Event,To,Task) -local text=string.format("Task done: %s ID=%d",Task.description,Task.id) -self:T(self.lid..text) -if Task.id==self.taskcurrent then -self.taskcurrent=0 -end -Task.status=OPSGROUP.TaskStatus.DONE -if Task.backupROE then -self:SwitchROE(Task.backupROE) -end -local Mission=self:GetMissionByTaskID(Task.id) -if Mission and Mission:IsNotOver()then -local status=Mission:GetGroupStatus(self) -if status~=AUFTRAG.GroupStatus.PAUSED then -if Mission.type==AUFTRAG.Type.CAPTUREZONE and Mission:CountMissionTargets()>0 then -self:T(self.lid.."Remove mission waypoints") -self:_RemoveMissionWaypoints(Mission,false) -if self:IsFlightgroup()then -else -self:T(self.lid.."Task done ==> Route to mission for next opszone") -self:MissionStart(Mission) -return -end -end -local EgressUID=Mission:GetGroupEgressWaypointUID(self) -if EgressUID then -self:T(self.lid..string.format("Task Done but Egress waypoint defined ==> Will call Mission Done once group passed waypoint UID=%d!",EgressUID)) -else -self:T(self.lid.."Task Done ==> Mission Done!") -self:MissionDone(Mission) -end -else -if self:IsOnMission(Mission.auftragsnummer)then -self.currentmission=nil -end -self:T(self.lid.."Remove mission waypoints") -self:_RemoveMissionWaypoints(Mission,false) -end -else -if Task.description=="Engage_Target"then -self:T(self.lid.."Task DONE Engage_Target ==> Cruise") -self:Disengage() -end -if Task.description==AUFTRAG.SpecialTask.ONGUARD or Task.description==AUFTRAG.SpecialTask.ARMOREDGUARD or Task.description==AUFTRAG.SpecialTask.NOTHING then -self:T(self.lid.."Task DONE OnGuard ==> Cruise") -self:Cruise() -end -if Task.description=="Task_Land_At"then -self:T(self.lid.."Taske DONE Task_Land_At ==> Wait") -self:Cruise() -self:Wait(20,100) -else -self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec") -self:_CheckGroupDone(1) -end -end -end -function OPSGROUP:AddMission(Mission) -Mission:AddOpsGroup(self) -Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.SCHEDULED) -Mission:Scheduled() -Mission.Nelements=Mission.Nelements+#self.elements -Mission.Ngroups=Mission.Ngroups+1 -table.insert(self.missionqueue,Mission) -self.adinfinitum=Mission.DCStask.params.adinfinitum and Mission.DCStask.params.adinfinitum or false -local text=string.format("Added %s mission %s starting at %s, stopping at %s", -tostring(Mission.type),tostring(Mission.name),UTILS.SecondsToClock(Mission.Tstart,true),Mission.Tstop and UTILS.SecondsToClock(Mission.Tstop,true)or"INF") -self:T(self.lid..text) -return self -end -function OPSGROUP:RemoveMission(Mission) -for i=#self.missionqueue,1,-1 do -local mission=self.missionqueue[i] -if mission.auftragsnummer==Mission.auftragsnummer then -local Task=Mission:GetGroupWaypointTask(self) -if Task then -self:RemoveTask(Task) -end -for j=#self.pausedmissions,1,-1 do -local mid=self.pausedmissions[j] -if Mission.auftragsnummer==mid then -table.remove(self.pausedmissions,j) -end -end -table.remove(self.missionqueue,i) -return self -end -end -return self -end -function OPSGROUP:CancelAllMissions() -self:T(self.lid.."Cancelling ALL missions!") -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -local mystatus=mission:GetGroupStatus(self) -if not(mystatus==AUFTRAG.GroupStatus.DONE or mystatus==AUFTRAG.GroupStatus.CANCELLED)then -self:T(self.lid.."Cancelling mission "..tostring(mission:GetName())) -self:MissionCancel(mission) -end -end -end -function OPSGROUP:CountRemainingMissison() -local N=0 -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -if mission and mission:IsNotOver()then -local status=mission:GetGroupStatus(self) -if status~=AUFTRAG.GroupStatus.DONE and status~=AUFTRAG.GroupStatus.CANCELLED then -N=N+1 -end -end -end -return N -end -function OPSGROUP:CountRemainingTransports() -local N=0 -for _,_transport in pairs(self.cargoqueue)do -local transport=_transport -local mystatus=transport:GetCarrierTransportStatus(self) -local status=transport:GetState() -self:T(self.lid..string.format("Transport my status=%s [%s]",mystatus,status)) -if transport and mystatus==OPSTRANSPORT.Status.SCHEDULED and status~=OPSTRANSPORT.Status.DELIVERED and status~=OPSTRANSPORT.Status.CANCELLED then -N=N+1 -end -end -if N==0 and self.cargoTransport and -self.cargoTransport:GetState()~=OPSTRANSPORT.Status.DELIVERED and self.cargoTransport:GetCarrierTransportStatus(self)~=OPSTRANSPORT.Status.DELIVERED and -self.cargoTransport:GetState()~=OPSTRANSPORT.Status.CANCELLED and self.cargoTransport:GetCarrierTransportStatus(self)~=OPSTRANSPORT.Status.CANCELLED then -N=1 -end -return N -end -function OPSGROUP:_GetNextMission() -if self:IsPickingup()or self:IsLoading()or self:IsTransporting()or self:IsUnloading()or self:IsLoaded()then -return nil -end -local Nmissions=#self.missionqueue -if Nmissions==0 then -return nil -end -local function _sort(a,b) -local taskA=a -local taskB=b -return(taskA.prio3.6 or true then -self:RouteToMission(Mission,3) -else -self:T(self.lid.."Immobile GROUP!") -local Clock=Mission.Tpush and UTILS.SecondsToClock(Mission.Tpush)or 5 -local Task=self:AddTask(Mission.DCStask,Clock,Mission.name,Mission.prio,Mission.duration) -Task.ismission=true -Mission:SetGroupWaypointTask(self,Task) -self:__TaskExecute(3,Task) -end -end -function OPSGROUP:onafterMissionExecute(From,Event,To,Mission) -local text=string.format("Executing %s Mission %s, target %s",Mission.type,tostring(Mission.name),Mission:GetTargetName()) -self:T(self.lid..text) -Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.EXECUTING) -Mission:Executing() -if self:IsHolding()and not self:HasPassedFinalWaypoint()then -self:Cruise() -end -if Mission.engagedetectedOn then -self:SetEngageDetectedOn(UTILS.MetersToNM(Mission.engagedetectedRmax),Mission.engagedetectedTypes,Mission.engagedetectedEngageZones,Mission.engagedetectedNoEngageZones) -end -if self.isFlightgroup then -if Mission.prohibitABExecute==true then -self:SetProhibitAfterburner() -self:T(self.lid.."Set prohibit AB") -elseif Mission.prohibitABExecute==false then -self:SetAllowAfterburner() -self:T2(self.lid.."Set allow AB") -end -end -end -function OPSGROUP:onafterPauseMission(From,Event,To) -local Mission=self:GetMissionCurrent() -if Mission then -Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.PAUSED) -local Task=Mission:GetGroupWaypointTask(self) -self:T(self.lid..string.format("Pausing current mission %s. Task=%s",tostring(Mission.name),tostring(Task and Task.description or"WTF"))) -self:TaskCancel(Task) -self:_RemoveMissionWaypoints(Mission) -table.insert(self.pausedmissions,1,Mission.auftragsnummer) -end -end -function OPSGROUP:onafterUnpauseMission(From,Event,To) -local mission=self:_GetPausedMission() -if mission then -self:T(self.lid..string.format("Unpausing mission %s [%s]",mission:GetName(),mission:GetType())) -self:MissionStart(mission) -for i,mid in pairs(self.pausedmissions)do -if mid==mission.auftragsnummer then -self:T(self.lid..string.format("Removing paused mission id=%d",mid)) -table.remove(self.pausedmissions,i) -break -end -end -else -self:T(self.lid.."ERROR: No mission to unpause!") -end -end -function OPSGROUP:onafterMissionCancel(From,Event,To,Mission) -if self:IsOnMission(Mission.auftragsnummer)then -local Task=Mission:GetGroupWaypointTask(self) -if Task then -self:T(self.lid..string.format("Cancel current mission %s. Task=%s",tostring(Mission.name),tostring(Task and Task.description or"WTF"))) -self:TaskCancel(Task) -else -self:MissionDone(Mission) -end -else -Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.CANCELLED) -self:RemoveMission(Mission) -self:_CheckGroupDone(1) -end -end -function OPSGROUP:_RemoveMissionWaypoints(Mission,Silently) -for i=#self.waypoints,1,-1 do -local wp=self.waypoints[i] -if wp.missionUID==Mission.auftragsnummer then -if Silently then -table.remove(self.waypoints,i) -else -self:RemoveWaypoint(i) -end -end -end -end -function OPSGROUP:onafterMissionDone(From,Event,To,Mission) -local text=string.format("Mission %s DONE!",Mission.name) -self:T(self.lid..text) -Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.DONE) -if self:IsOnMission(Mission.auftragsnummer)then -self.currentmission=nil -end -self:_RemoveMissionWaypoints(Mission) -if Mission.patroldata then -Mission.patroldata.noccupied=Mission.patroldata.noccupied-1 -AIRWING.UpdatePatrolPointMarker(Mission.patroldata) -end -if Mission.engagedetectedOn then -self:SetEngageDetectedOff() -end -if Mission.optionROE then -self:SwitchROE() -end -if self:IsFlightgroup()and Mission.optionROT then -self:SwitchROT() -end -if Mission.optionAlarm then -self:SwitchAlarmstate() -end -if Mission.optionEPLRS then -self:SwitchEPLRS() -end -if Mission.optionEmission then -self:SwitchEmission() -end -if Mission.optionInvisible then -self:SwitchInvisible() -end -if Mission.optionImmortal then -self:SwitchImmortal() -end -if Mission.optionFormation and self:IsFlightgroup()then -self:SwitchFormation() -end -if Mission.radio then -self:SwitchRadio() -end -if Mission.tacan then -self:_SwitchTACAN() -local cohort=self.cohort -if cohort then -cohort:ReturnTacan(Mission.tacan.Channel) -end -local asset=Mission:GetAssetByName(self.groupname) -if asset then -asset.tacan=nil -end -end -if Mission.icls then -self:_SwitchICLS() -end -if self.legion and Mission.legionReturn~=nil then -self:SetReturnToLegion(Mission.legionReturn) -end -local delay=1 -if Mission.type==AUFTRAG.Type.ARTY then -delay=60 -elseif Mission.type==AUFTRAG.Type.RELOCATECOHORT then -local legion=Mission.DCStask.params.legion -self:T(self.lid..string.format("Asset relocated to new legion=%s",tostring(legion.alias))) -local asset=Mission:GetAssetByName(self.groupname) -if asset then -asset.wid=legion.uid -end -self.legion=legion -if self.isArmygroup then -self:T2(self.lid.."Adding asset via ReturnToLegion()") -self:ReturnToLegion() -elseif self.isFlightgroup then -self:T2(self.lid.."Adding asset via RTB to new legion airbase") -self:RTB(self.legion.airbase) -end -return -end -if self.isFlightgroup then -if Mission.prohibitAB==true then -self:T2("Setting prohibit AB") -self:SetProhibitAfterburner() -elseif Mission.prohibitAB==false then -self:T2("Setting allow AB") -self:SetAllowAfterburner() -end -end -if self.legion and self.legionReturn==false and self.waypoints and#self.waypoints==1 then -local Coordinate=self:GetCoordinate() -if self.isArmygroup then -ARMYGROUP.AddWaypoint(self,Coordinate,0,nil,nil,false) -elseif self.isNavygroup then -NAVYGROUP.AddWaypoint(self,Coordinate,0,nil,nil,false) -end -self:RemoveWaypoint(1) -self:_PassedFinalWaypoint(true,"Passed final waypoint as group is done with mission but should NOT return to its legion") -end -self:_CheckGroupDone(delay) -end -function OPSGROUP:RouteToMission(mission,delay) -if delay and delay>0 then -self:ScheduleOnce(delay,OPSGROUP.RouteToMission,self,mission) -else -self:T(self.lid..string.format("Route To Mission")) -if self:IsDead()or self:IsStopped()then -self:T(self.lid..string.format("Route To Mission: I am DEAD or STOPPED! Ooops...")) -return -end -if self:IsCargo()then -self:T(self.lid..string.format("Route To Mission: I am CARGO! You cannot route me...")) -return -end -if mission.type==AUFTRAG.Type.OPSTRANSPORT then -self:T(self.lid..string.format("Route To Mission: I am OPSTRANSPORT! Add transport and return...")) -self:AddOpsTransport(mission.opstransport) -return -end -if mission.type==AUFTRAG.Type.ALERT5 then -self:T(self.lid..string.format("Route To Mission: I am ALERT5! Go right to MissionExecute()...")) -self:MissionExecute(mission) -return -end -local uid=self:GetWaypointCurrentUID() -local waypointcoord=nil -local currentcoord=self:GetCoordinate() -local roadcoord=currentcoord:GetClosestPointToRoad() -local roaddist=nil -if roadcoord then -roaddist=currentcoord:Get2DDistance(roadcoord) -end -local targetzone=nil -local randomradius=mission.missionWaypointRadius or 1000 -local surfacetypes=nil -if self:IsArmygroup()then -surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD} -elseif self:IsNavygroup()then -surfacetypes={land.SurfaceType.WATER,land.SurfaceType.SHALLOW_WATER} -end -local targetobject=mission:GetObjective(currentcoord,UTILS.GetCoalitionEnemy(self:GetCoalition(),true)) -if targetobject then -self:T(self.lid..string.format("Route to mission target object %s",targetobject:GetName())) -end -if mission.opstransport and not mission.opstransport:IsCargoDelivered(self.groupname)then -local tzc=mission.opstransport:GetTZCofCargo(self.groupname) -local pickupzone=tzc.PickupZone -if self:IsInZone(pickupzone)then -self:PauseMission() -self:FullStop() -return -else -waypointcoord=pickupzone:GetRandomCoordinate() -end -elseif mission.type==AUFTRAG.Type.PATROLZONE or -mission.type==AUFTRAG.Type.BARRAGE or -mission.type==AUFTRAG.Type.AMMOSUPPLY or -mission.type==AUFTRAG.Type.FUELSUPPLY or -mission.type==AUFTRAG.Type.REARMING or -mission.type==AUFTRAG.Type.AIRDEFENSE or -mission.type==AUFTRAG.Type.EWR then -targetzone=targetobject -waypointcoord=targetzone:GetRandomCoordinate(nil,nil,surfacetypes) -elseif mission.type==AUFTRAG.Type.ONGUARD or mission.type==AUFTRAG.Type.ARMOREDGUARD then -waypointcoord=mission:GetMissionWaypointCoord(self.group,nil,surfacetypes) -elseif mission.type==AUFTRAG.Type.NOTHING then -targetzone=targetobject -waypointcoord=targetzone:GetRandomCoordinate(nil,nil,surfacetypes) -elseif mission.type==AUFTRAG.Type.HOVER then -local zone=targetobject -waypointcoord=zone:GetCoordinate() -elseif mission.type==AUFTRAG.Type.RELOCATECOHORT then -local ToCoordinate=mission.DCStask.params.legion:GetCoordinate() -if self.isFlightgroup then -waypointcoord=currentcoord:GetIntermediateCoordinate(ToCoordinate,0.2):SetAltitude(self.altitudeCruise) -elseif self.isArmygroup then -if roadcoord then -waypointcoord=roadcoord -else -waypointcoord=currentcoord:GetIntermediateCoordinate(ToCoordinate,100) -end -else -waypointcoord=currentcoord:GetIntermediateCoordinate(ToCoordinate,0.05) -end -elseif mission.type==AUFTRAG.Type.CAPTUREZONE then -targetzone=targetobject:GetZone() -waypointcoord=targetzone:GetRandomCoordinate(nil,nil,surfacetypes) -else -waypointcoord=mission:GetMissionWaypointCoord(self.group,randomradius,surfacetypes) -end -for _,task in pairs(mission.enrouteTasks)do -self:AddTaskEnroute(task) -end -local SpeedToMission=mission.missionSpeed and UTILS.KmphToKnots(mission.missionSpeed)or self:GetSpeedCruise() -if mission.type==AUFTRAG.Type.TROOPTRANSPORT then -mission.DCStask=mission:GetDCSMissionTask(self.group) -local pradius=mission.transportPickupRadius -local pickupZone=ZONE_RADIUS:New("Pickup Zone",mission.transportPickup:GetVec2(),pradius) -for _,_group in pairs(mission.transportGroupSet.Set)do -local group=_group -if group and group:IsAlive()then -local pcoord=pickupZone:GetRandomCoordinate(20,pradius,{land.SurfaceType.LAND,land.SurfaceType.ROAD}) -local DCSTask=group:TaskEmbarkToTransport(pcoord,pradius) -group:SetTask(DCSTask,5) -end -end -elseif mission.type==AUFTRAG.Type.ARTY then -local targetcoord=mission:GetTargetCoordinate() -local inRange=self:InWeaponRange(targetcoord,mission.engageWeaponType) -if inRange then -waypointcoord=self:GetCoordinate(true) -else -local coordInRange=self:GetCoordinateInRange(targetcoord,mission.engageWeaponType,waypointcoord) -if coordInRange then -local waypoint=nil -if self:IsFlightgroup()then -waypoint=FLIGHTGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false) -elseif self:IsArmygroup()then -waypoint=ARMYGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,mission.optionFormation,false) -elseif self:IsNavygroup()then -waypoint=NAVYGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false) -end -waypoint.missionUID=mission.auftragsnummer -waypointcoord=coordInRange -uid=waypoint.uid -end -end -end -local d=currentcoord:Get2DDistance(waypointcoord) -self:T(self.lid..string.format("Distance to ingress waypoint=%.1f m",d)) -local waypoint=nil -if self:IsFlightgroup()then -waypoint=FLIGHTGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false) -elseif self:IsArmygroup()then -local formation=mission.optionFormation -if d<1000 or mission.type==AUFTRAG.Type.RELOCATECOHORT then -formation=ENUMS.Formation.Vehicle.OffRoad -end -waypoint=ARMYGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,formation,false) -elseif self:IsNavygroup()then -waypoint=NAVYGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false) -end -waypoint.missionUID=mission.auftragsnummer -local waypointtask=self:AddTaskWaypoint(mission.DCStask,waypoint,mission.name,mission.prio,mission.duration) -waypointtask.ismission=true -waypointtask.target=targetobject -mission:SetGroupWaypointTask(self,waypointtask) -mission:SetGroupWaypointIndex(self,waypoint.uid) -local egresscoord=mission:GetMissionEgressCoord() -if egresscoord then -local Ewaypoint=nil -if self:IsFlightgroup()then -Ewaypoint=FLIGHTGROUP.AddWaypoint(self,egresscoord,SpeedToMission,waypoint.uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false) -elseif self:IsArmygroup()then -Ewaypoint=ARMYGROUP.AddWaypoint(self,egresscoord,SpeedToMission,waypoint.uid,mission.optionFormation,false) -elseif self:IsNavygroup()then -Ewaypoint=NAVYGROUP.AddWaypoint(self,egresscoord,SpeedToMission,waypoint.uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false) -end -Ewaypoint.missionUID=mission.auftragsnummer -mission:SetGroupEgressWaypointUID(self,Ewaypoint.uid) -end -if targetzone and self:IsInZone(targetzone)then -self:T(self.lid.."Already in mission zone ==> TaskExecute()") -self:TaskExecute(waypointtask) -return -elseif d<25 then -self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()") -self:TaskExecute(waypointtask) -return -end -if self.speedMax<=3.6 or mission.teleport then -self:Teleport(waypointcoord,nil,true) -self:__TaskExecute(-1,waypointtask) -else -if self:IsArmygroup()then -self:Cruise(SpeedToMission) -elseif self:IsNavygroup()then -self:Cruise(SpeedToMission) -elseif self:IsFlightgroup()then -self:UpdateRoute() -end -end -self:_SetMissionOptions(mission) -end -end -function OPSGROUP:_SetMissionOptions(mission) -if mission.optionROE then -self:SwitchROE(mission.optionROE) -end -if mission.optionROT then -self:SwitchROT(mission.optionROT) -end -if mission.optionAlarm then -self:SwitchAlarmstate(mission.optionAlarm) -end -if mission.optionEPLRS then -self:SwitchEPLRS(mission.optionEPLRS) -end -if mission.optionEmission then -self:SwitchEmission(mission.optionEmission) -end -if mission.optionInvisible then -self:SwitchInvisible(mission.optionInvisible) -end -if mission.optionImmortal then -self:SwitchImmortal(mission.optionImmortal) -end -if mission.optionFormation and self:IsFlightgroup()then -self:SwitchFormation(mission.optionFormation) -end -if mission.radio then -self:SwitchRadio(mission.radio.Freq,mission.radio.Modu) -end -if mission.tacan then -self:SwitchTACAN(mission.tacan.Channel,mission.tacan.Morse,mission.tacan.BeaconName,mission.tacan.Band) -end -if mission.icls then -self:SwitchICLS(mission.icls.Channel,mission.icls.Morse,mission.icls.UnitName) -end -if self.isFlightgroup then -if mission.prohibitAB==true then -self:SetProhibitAfterburner() -self:T2("Set prohibit AB") -elseif mission.prohibitAB==false then -self:SetAllowAfterburner() -self:T2("Set allow AB") -end -end -return self -end -function OPSGROUP:_QueueUpdate() -if self:IsExist()then -local mission=self:_GetNextMission() -if mission then -local currentmission=self:GetMissionCurrent() -if currentmission then -if mission.urgent and mission.prio0 then -self:T(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!")) -Tsuspend=-30 -allowed=false -end -if self.cargoTransport then -self:T(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) -Tsuspend=-30 -allowed=false -end -if Tsuspend and not allowed then -self:__Wait(Tsuspend,Duration) -end -return allowed -end -function OPSGROUP:onafterWait(From,Event,To,Duration) -self:FullStop() -self.Twaiting=timer.getAbsTime() -self.dTwait=Duration -end -function OPSGROUP:onafterPassingWaypoint(From,Event,To,Waypoint) -local task=self:GetTaskCurrent() -local mission=nil -if task then -mission=self:GetMissionByTaskID(task.id) -end -if task and task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then -self:RemoveWaypointByID(Waypoint.uid) -local zone=task.dcstask.params.zone -local surfacetypes=nil -if self:IsArmygroup()then -surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD} -elseif self:IsNavygroup()then -surfacetypes={land.SurfaceType.WATER,land.SurfaceType.SHALLOW_WATER} -end -local Coordinate=zone:GetRandomCoordinate(nil,nil,surfacetypes) -local Speed=task.dcstask.params.speed and UTILS.MpsToKnots(task.dcstask.params.speed)or UTILS.KmphToKnots(self.speedCruise) -local Altitude=UTILS.MetersToFeet(task.dcstask.params.altitude or self.altitudeCruise) -local currUID=self:GetWaypointCurrent().uid -local wp=nil -if self.isFlightgroup then -wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) -elseif self.isArmygroup then -wp=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,task.dcstask.params.formation) -elseif self.isNavygroup then -wp=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) -end -wp.missionUID=mission and mission.auftragsnummer or nil -elseif task and task.dcstask.id==AUFTRAG.SpecialTask.RECON then -local target=task.dcstask.params.target -if self.adinfinitum and#self.reconindecies==0 then -self.reconindecies={} -for i=1,#target.targets do -table.insert(self.reconindecies,i) -end -end -if#self.reconindecies>0 then -local n=1 -if task.dcstask.params.randomly then -n=UTILS.GetRandomTableElement(self.reconindecies) -else -n=self.reconindecies[1] -table.remove(self.reconindecies,1) -end -local object=target.targets[n] -local zone=object.Object -local Coordinate=zone:GetRandomCoordinate() -local Speed=task.dcstask.params.speed and UTILS.MpsToKnots(task.dcstask.params.speed)or UTILS.KmphToKnots(self.speedCruise) -local Altitude=task.dcstask.params.altitude and UTILS.MetersToFeet(task.dcstask.params.altitude)or nil -local currUID=self:GetWaypointCurrent().uid -local wp=nil -if self.isFlightgroup then -wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) -elseif self.isArmygroup then -wp=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,task.dcstask.params.formation) -elseif self.isNavygroup then -wp=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) -end -wp.missionUID=mission and mission.auftragsnummer or nil -else -local wpindex=self:GetWaypointIndex(Waypoint.uid) -if wpindex==nil or wpindex==#self.waypoints then -if not self.adinfinitum or#self.waypoints<=1 then -self:_PassedFinalWaypoint(true,"Passing waypoint and NOT adinfinitum and #self.waypoints<=1") -end -end -self:TaskDone(task) -end -elseif task and task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then -local legion=task.dcstask.params.legion -self:T(self.lid..string.format("Asset arrived at relocation task waypoint ==> Task Done!")) -self:TaskDone(task) -elseif task and task.dcstask.id==AUFTRAG.SpecialTask.REARMING then -self:T(self.lid..string.format("FF Rearming Mission ==> Rearm()")) -self:Rearm() -else -local ntasks=self:_SetWaypointTasks(Waypoint) -local wpindex=self:GetWaypointIndex(Waypoint.uid) -if wpindex==nil or wpindex==#self.waypoints then -if self.adinfinitum then -if Waypoint.missionUID then -else -if#self.waypoints<=1 then -self:_PassedFinalWaypoint(true,"PassingWaypoint: adinfinitum but only ONE WAYPOINT left") -else -self:__UpdateRoute(-0.01,1,1) -end -end -else -self:_PassedFinalWaypoint(true,"PassingWaypoint: wpindex=#self.waypoints (or wpindex=nil)") -end -elseif wpindex==1 then -if self.adinfinitum then -if#self.waypoints<=1 then -self:_PassedFinalWaypoint(true,"PassingWaypoint: adinfinitum but only ONE WAYPOINT left") -else -if not Waypoint.missionUID then -self:__UpdateRoute(-0.01,2) -end -end -end -end -local isEgress=false -if Waypoint.missionUID then -self:T2(self.lid..string.format("Passing mission waypoint UID=%s",tostring(Waypoint.uid))) -local mission=self:GetMissionByID(Waypoint.missionUID) -local EgressUID=mission and mission:GetGroupEgressWaypointUID(self)or nil -isEgress=EgressUID and Waypoint.uid==EgressUID -if isEgress and mission:GetGroupStatus(self)~=AUFTRAG.GroupStatus.DONE then -self:MissionDone(mission) -end -end -if ntasks==0 and self:HasPassedFinalWaypoint()and not isEgress then -self:_CheckGroupDone(0.01) -end -local text=string.format("Group passed waypoint %s/%d ID=%d: final=%s detour=%s astar=%s", -tostring(wpindex),#self.waypoints,Waypoint.uid,tostring(self.passedfinalwp),tostring(Waypoint.detour),tostring(Waypoint.astar)) -self:T(self.lid..text) -end -local wpnext=self:GetWaypointNext() -if wpnext then -self.speedWp=wpnext.speed -end -end -function OPSGROUP:_SetWaypointTasks(Waypoint) -local tasks=self:GetTasksWaypoint(Waypoint.uid) -local text=string.format("WP uid=%d tasks:",Waypoint.uid) -local missiontask=nil -if#tasks>0 then -for i,_task in pairs(tasks)do -local task=_task -text=text..string.format("\n[%d] %s",i,task.description) -if task.ismission then -missiontask=task -end -end -else -text=text.." None" -end -self:T(self.lid..text) -if missiontask then -self:T(self.lid.."Executing mission task") -local mission=self:GetMissionByTaskID(missiontask.id) -if mission then -if mission.opstransport and not mission.opstransport:IsCargoDelivered(self.groupname)then -self:PauseMission() -return -end -end -self:TaskExecute(missiontask) -return 1 -end -local taskswp={} -for _,task in pairs(tasks)do -local Task=task -table.insert(taskswp,self.group:TaskFunction("OPSGROUP._TaskExecute",self,Task)) -local TaskCondition=self.group:TaskCondition(nil,Task.stopflag:GetName(),1,nil,Task.duration) -table.insert(taskswp,self.group:TaskControlled(Task.dcstask,TaskCondition)) -table.insert(taskswp,self.group:TaskFunction("OPSGROUP._TaskDone",self,Task)) -end -if#taskswp>0 then -self:SetTask(self.group:TaskCombo(taskswp)) -end -return#taskswp -end -function OPSGROUP:onafterPassedFinalWaypoint(From,Event,To) -self:T(self.lid..string.format("Group passed final waypoint")) -end -function OPSGROUP:onafterGotoWaypoint(From,Event,To,UID,Speed) -local n=self:GetWaypointIndex(UID) -if n then -Speed=Speed or self:GetSpeedToWaypoint(n) -self:T(self.lid..string.format("Goto Waypoint UID=%d index=%d from %d at speed %.1f knots",UID,n,self.currentwp,Speed)) -self:__UpdateRoute(0.1,n,nil,Speed) -end -end -function OPSGROUP:onafterDetectedUnit(From,Event,To,Unit) -local unitname=Unit and Unit:GetName()or"unknown" -self:T2(self.lid..string.format("Detected unit %s",unitname)) -if self.detectedunits:FindUnit(unitname)then -self:DetectedUnitKnown(Unit) -else -self:DetectedUnitNew(Unit) -end -end -function OPSGROUP:onafterDetectedUnitNew(From,Event,To,Unit) -self:T(self.lid..string.format("Detected New unit %s",Unit:GetName())) -self.detectedunits:AddUnit(Unit) -end -function OPSGROUP:onafterDetectedGroup(From,Event,To,Group) -local groupname=Group and Group:GetName()or"unknown" -self:T(self.lid..string.format("Detected group %s",groupname)) -if self.detectedgroups:FindGroup(groupname)then -self:DetectedGroupKnown(Group) -else -self:DetectedGroupNew(Group) -end -end -function OPSGROUP:onafterDetectedGroupNew(From,Event,To,Group) -self:T(self.lid..string.format("Detected New group %s",Group:GetName())) -self.detectedgroups:AddGroup(Group) -end -function OPSGROUP:onafterEnterZone(From,Event,To,Zone) -local zonename=Zone and Zone:GetName()or"unknown" -self:T2(self.lid..string.format("Entered Zone %s",zonename)) -self.inzones:Add(Zone:GetName(),Zone) -end -function OPSGROUP:onafterLeaveZone(From,Event,To,Zone) -local zonename=Zone and Zone:GetName()or"unknown" -self:T2(self.lid..string.format("Left Zone %s",zonename)) -self.inzones:Remove(zonename,true) -end -function OPSGROUP:onbeforeLaserOn(From,Event,To,Target) -if self.spot.On then -return false -end -if Target then -self:SetLaserTarget(Target) -else -self:T(self.lid.."ERROR: No target provided for LASER!") -return false -end -local element=self:GetElementAlive() -if element then -self.spot.element=element -local offsetY=2 -if self.isFlightgroup or self.isNavygroup then -offsetY=element.height -end -self.spot.offset={x=0,y=offsetY,z=0} -if self.spot.CheckLOS then -local los=self:HasLoS(self.spot.Coordinate,self.spot.element,self.spot.offset) -if los then -self:LaserGotLOS() -else -self:T(self.lid.."LASER got no LOS currently. Trying to switch the laser on again in 10 sec") -self:__LaserOn(-10,Target) -return false -end -end -else -self:T(self.lid.."ERROR: No element alive for lasing") -return false -end -return true -end -function OPSGROUP:onafterLaserOn(From,Event,To,Target) -if not self.spot.timer:IsRunning()then -self.spot.timer:Start(nil,self.spot.dt) -end -local DCSunit=self.spot.element.unit:GetDCSObject() -self.spot.Laser=Spot.createLaser(DCSunit,self.spot.offset,self.spot.vec3,self.spot.Code or 1688) -if self.spot.IRon then -self.spot.IR=Spot.createInfraRed(DCSunit,self.spot.offset,self.spot.vec3) -end -self.spot.On=true -self.spot.Paused=false -self:T(self.lid.."Switching LASER on") -end -function OPSGROUP:onbeforeLaserOff(From,Event,To) -return self.spot.On or self.spot.Paused -end -function OPSGROUP:onafterLaserOff(From,Event,To) -self:T(self.lid.."Switching LASER off") -if self.spot.On then -self.spot.Laser:destroy() -self.spot.IR:destroy() -self.spot.Laser=nil -self.spot.IR=nil -end -self.spot.timer:Stop() -self.spot.TargetUnit=nil -self.spot.On=false -self.spot.Paused=false -end -function OPSGROUP:onafterLaserPause(From,Event,To) -self:T(self.lid.."Switching LASER off temporarily") -self.spot.Laser:destroy() -self.spot.IR:destroy() -self.spot.Laser=nil -self.spot.IR=nil -self.spot.On=false -self.spot.Paused=true -end -function OPSGROUP:onbeforeLaserResume(From,Event,To) -return self.spot.Paused -end -function OPSGROUP:onafterLaserResume(From,Event,To) -self:T(self.lid.."Resuming LASER") -self.spot.Paused=false -local target=nil -if self.spot.TargetType==0 then -target=self.spot.Coordinate -elseif self.spot.TargetType==1 or self.spot.TargetType==2 then -target=self.spot.TargetUnit -elseif self.spot.TargetType==3 then -target=self.spot.TargetGroup -end -if target then -self:T(self.lid.."Switching LASER on again") -self:LaserOn(target) -end -end -function OPSGROUP:onafterLaserCode(From,Event,To,Code) -self.spot.Code=Code or 1688 -self:T2(self.lid..string.format("Setting LASER Code to %d",self.spot.Code)) -if self.spot.On then -self:T(self.lid..string.format("New LASER Code is %d",self.spot.Code)) -self.spot.Laser:setCode(self.spot.Code) -end -end -function OPSGROUP:onafterLaserLostLOS(From,Event,To) -self.spot.LOS=false -self.spot.lostLOS=true -if self.spot.On then -self:LaserPause() -end -end -function OPSGROUP:onafterLaserGotLOS(From,Event,To) -self.spot.LOS=true -if self.spot.lostLOS then -self.spot.lostLOS=false -if self.spot.Paused then -self:LaserResume() -end -end -end -function OPSGROUP:SetLaserTarget(Target) -if Target then -if Target:IsInstanceOf("SCENERY")then -self.spot.TargetType=0 -self.spot.offsetTarget={x=0,y=3,z=0} -elseif Target:IsInstanceOf("POSITIONABLE")then -local target=Target -if target:IsAlive()then -if target:IsInstanceOf("GROUP")then -self.spot.TargetGroup=target -self.spot.TargetUnit=target:GetHighestThreat() -self.spot.TargetType=3 -else -self.spot.TargetUnit=target -if target:IsInstanceOf("STATIC")then -self.spot.TargetType=1 -elseif target:IsInstanceOf("UNIT")then -self.spot.TargetType=2 -end -end -local size,x,y,z=self.spot.TargetUnit:GetObjectSize() -if y then -self.spot.offsetTarget={x=0,y=y*0.75,z=0} -else -self.spot.offsetTarget={x=0,2,z=0} -end -else -self:T("WARNING: LASER target is not alive!") -return -end -elseif Target:IsInstanceOf("COORDINATE")then -self.spot.TargetType=0 -self.spot.offsetTarget={x=0,y=0,z=0} -else -self:T(self.lid.."ERROR: LASER target should be a POSITIONABLE (GROUP, UNIT or STATIC) or a COORDINATE object!") -return -end -self.spot.vec3=UTILS.VecAdd(Target:GetVec3(),self.spot.offsetTarget) -self.spot.Coordinate:UpdateFromVec3(self.spot.vec3) -end -end -function OPSGROUP:_UpdateLaser() -if self.spot.TargetUnit then -if self.spot.TargetUnit:IsAlive()then -local vec3=self.spot.TargetUnit:GetVec3() -vec3=UTILS.VecAdd(vec3,self.spot.offsetTarget) -local dist=UTILS.VecDist3D(vec3,self.spot.vec3) -self.spot.vec3=vec3 -self.spot.Coordinate:UpdateFromVec3(vec3) -if dist>1 then -if self.spot.On then -self.spot.Laser:setPoint(vec3) -if self.spot.IRon then -self.spot.IR:setPoint(vec3) -end -end -end -else -if self.spot.TargetGroup and self.spot.TargetGroup:IsAlive()then -local unit=self.spot.TargetGroup:GetHighestThreat() -if unit then -self:T(self.lid..string.format("Switching to target unit %s in the group",unit:GetName())) -self.spot.TargetUnit=unit -return -else -self:T(self.lid.."Target is not alive any more ==> switching LASER off") -self:LaserOff() -return -end -else -self:T(self.lid.."Target is not alive any more ==> switching LASER off") -self:LaserOff() -return -end -end -end -if self.spot.CheckLOS then -local los=self:HasLoS(self.spot.Coordinate,self.spot.element,self.spot.offset) -if los then -if self.spot.lostLOS then -self:LaserGotLOS() -end -else -if not self.spot.lostLOS then -self:LaserLostLOS() -end -end -end -end -function OPSGROUP:onbeforeElementSpawned(From,Event,To,Element) -if Element and Element.status==OPSGROUP.ElementStatus.SPAWNED then -self:T2(self.lid..string.format("Element %s is already spawned ==> Transition denied!",Element.name)) -return false -end -return true -end -function OPSGROUP:onafterElementInUtero(From,Event,To,Element) -self:T(self.lid..string.format("Element in utero %s",Element.name)) -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.INUTERO) -end -function OPSGROUP:onafterElementDamaged(From,Event,To,Element) -self:T(self.lid..string.format("Element damaged %s",Element.name)) -if Element and(Element.status~=OPSGROUP.ElementStatus.DEAD and Element.status~=OPSGROUP.ElementStatus.INUTERO)then -local lifepoints=0 -if Element.DCSunit and Element.DCSunit:isExist()then -lifepoints=Element.DCSunit:getLife() -self:T(self.lid..string.format("Element life %s: %.2f/%.2f",Element.name,lifepoints,Element.life0)) -else -self:T(self.lid..string.format("Element.DCSunit %s does not exist!",Element.name)) -end -if lifepoints<=1.0 then -self:T(self.lid..string.format("Element %s life %.2f <= 1.0 ==> Destroyed!",Element.name,lifepoints)) -self:ElementDestroyed(Element) -end -end -end -function OPSGROUP:onafterElementHit(From,Event,To,Element,Enemy) -Element.Nhit=Element.Nhit+1 -self:T(self.lid..string.format("Element hit %s by %s [n=%d, N=%d]",Element.name,Enemy and Enemy:GetName()or"unknown",Element.Nhit,self.Nhit)) -self:__Hit(-3,Enemy) -end -function OPSGROUP:onafterHit(From,Event,To,Enemy) -self:T(self.lid..string.format("Group hit by %s",Enemy and Enemy:GetName()or"unknown")) -end -function OPSGROUP:onafterElementDestroyed(From,Event,To,Element) -self:T(self.lid..string.format("Element destroyed %s",Element.name)) -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -mission:ElementDestroyed(self,Element) -end -self.Ndestroyed=self.Ndestroyed+1 -self:ElementDead(Element) -end -function OPSGROUP:onafterElementDead(From,Event,To,Element) -self:I(self.lid..string.format("Element dead %s at t=%.3f",Element.name,timer.getTime())) -self:_UpdateStatus(Element,OPSGROUP.ElementStatus.DEAD) -if self.spot.On and self.spot.element.name==Element.name then -self:LaserOff() -if self:GetNelements()>0 then -local target=nil -if self.spot.TargetType==0 then -target=self.spot.Coordinate -elseif self.spot.TargetType==1 or self.spot.TargetType==2 then -if self.spot.TargetUnit and self.spot.TargetUnit:IsAlive()then -target=self.spot.TargetUnit -end -elseif self.spot.TargetType==3 then -if self.spot.TargetGroup and self.spot.TargetGroup:IsAlive()then -target=self.spot.TargetGroup -end -end -if target then -self:__LaserOn(-1,target) -end -end -end -for i=#Element.cargoBay,1,-1 do -local mycargo=Element.cargoBay[i] -if mycargo.group then -self:_DelCargobay(mycargo.group) -if mycargo.group and not(mycargo.group:IsDead()or mycargo.group:IsStopped())then -mycargo.group:_RemoveMyCarrier() -if mycargo.reserved then -mycargo.group:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) -else -for _,cargoelement in pairs(mycargo.group.elements)do -self:T2(self.lid.."Cargo element dead "..cargoelement.name) -mycargo.group:ElementDead(cargoelement) -end -end -end -else -if self.cargoTZC then -for _,_cargo in pairs(self.cargoTZC.Cargos)do -local cargo=_cargo -if cargo.uid==mycargo.cargoUID then -cargo.storage.cargoLost=cargo.storage.cargoLost+mycargo.storageAmount -end -end -end -self:_DelCargobayElement(Element,mycargo) -end -end -end -function OPSGROUP:onafterRespawn(From,Event,To,Template) -self:T(self.lid.."Respawning group!") -local template=UTILS.DeepCopy(Template or self.template) -template.lateActivation=false -self:_Respawn(0,template) -end -function OPSGROUP:Teleport(Coordinate,Delay,NoPauseMission) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,OPSGROUP.Teleport,self,Coordinate,0,NoPauseMission) -else -self:T(self.lid.."FF Teleporting...") -if self:IsOnMission()and not NoPauseMission then -self:T(self.lid.."Pausing current mission for telport") -self:PauseMission() -end -local Template=UTILS.DeepCopy(self.template) -Template.lateActivation=self:IsLateActivated() -Template.uncontrolled=false -if self:IsFlightgroup()then -Template.route.points[1]=Coordinate:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,300,true,nil,nil,"Spawnpoint") -elseif self:IsArmygroup()then -Template.route.points[1]=Coordinate:WaypointGround(0) -elseif self:IsNavygroup()then -Template.route.points[1]=Coordinate:WaypointNaval(0) -end -local units=Template.units -local d={} -for i=1,#units do -local unit=units[i] -d[i]={x=Coordinate.x+(units[i].x-units[1].x),y=Coordinate.z+units[i].y-units[1].y} -end -for i=#units,1,-1 do -local unit=units[i] -local element=self:GetElementByName(unit.name) -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -unit.parking=nil -unit.parking_id=nil -local vec3=element.unit:GetVec3() -local heading=element.unit:GetHeading() -unit.x=d[i].x -unit.y=d[i].y -unit.alt=Coordinate.y -unit.heading=math.rad(heading) -unit.psi=-unit.heading -else -table.remove(units,i) -end -end -self:_Respawn(0,Template,true) -end -end -function OPSGROUP:_Respawn(Delay,Template,Reset) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,OPSGROUP._Respawn,self,0,Template,Reset) -else -self:T2(self.lid.."FF _Respawn") -Template=Template or self:_GetTemplate(true) -self.Ndestroyed=0 -self.Nhit=0 -if self:IsAlive()then -local units=Template.units -for i=#units,1,-1 do -local unit=units[i] -local element=self:GetElementByName(unit.name) -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -if not Reset then -unit.parking=element.parking and element.parking.TerminalID or unit.parking -unit.parking_id=nil -local vec3=element.unit:GetVec3() -local heading=element.unit:GetHeading() -unit.x=vec3.x -unit.y=vec3.z -unit.alt=vec3.y -unit.heading=math.rad(heading) -unit.psi=-unit.heading -end -else -table.remove(units,i) -self.Ndestroyed=self.Ndestroyed+1 -end -end -self:Despawn(0,true) -else -for _,_element in pairs(self.elements)do -local element=_element -self:ElementInUtero(element) -end -end -self:T({Template=Template}) -self.group=_DATABASE:Spawn(Template) -self.dcsgroup=self:GetDCSGroup() -self.controller=self.dcsgroup:getController() -self.isLateActivated=Template.lateActivation -self.isUncontrolled=Template.uncontrolled -self.isDead=false -self.isDestroyed=false -self.groupinitialized=false -self.wpcounter=1 -self.currentwp=1 -self:_InitWaypoints() -self:_InitGroup(Template) -end -return self -end -function OPSGROUP:onafterInUtero(From,Event,To) -self:T(self.lid..string.format("Group inutero at t=%.3f",timer.getTime())) -end -function OPSGROUP:onafterDamaged(From,Event,To) -self:T(self.lid..string.format("Group damaged at t=%.3f",timer.getTime())) -end -function OPSGROUP:onafterDestroyed(From,Event,To) -self:T(self.lid..string.format("Group destroyed at t=%.3f",timer.getTime())) -self.isDestroyed=true -end -function OPSGROUP:onbeforeDead(From,Event,To) -if self.Ndestroyed==#self.elements then -self:Destroyed() -end -end -function OPSGROUP:onafterDead(From,Event,To) -self:T(self.lid..string.format("Group dead at t=%.3f",timer.getTime())) -self.isDead=true -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -self:T(self.lid.."Cancelling mission because group is dead! Mission name "..tostring(mission:GetName())) -self:MissionCancel(mission) -mission:GroupDead(self) -end -self:ClearWaypoints() -self.groupinitialized=false -self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO -self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER -local mycarrier=self:_GetMyCarrierGroup() -if mycarrier and not mycarrier:IsDead()then -mycarrier:_DelCargobay(self) -self:_RemoveMyCarrier() -end -for i,_transport in pairs(self.cargoqueue)do -local transport=_transport -transport:__DeadCarrierGroup(1,self) -end -self.cargoqueue={} -self.cargoTransport=nil -self.cargoTZC=nil -if self.Ndestroyed==#self.elements then -if self.cohort then -self.cohort:DelGroup(self.groupname) -end -else -end -if self.legion then -if not self:IsInUtero()then -local asset=self.legion:GetAssetByName(self.groupname) -local request=self.legion:GetRequestByID(asset.rid) -self.legion:AssetDead(asset,request) -end -self:__Stop(-5) -elseif not self.isAI then -self:__Stop(-1) -end -end -function OPSGROUP:onbeforeStop(From,Event,To) -if self:IsAlive()then -self:T(self.lid..string.format("WARNING: Group is still alive! Will not stop the FSM. Use :Despawn() instead")) -return false -end -return true -end -function OPSGROUP:onafterStop(From,Event,To) -self:UnHandleEvent(EVENTS.Birth) -self:UnHandleEvent(EVENTS.Dead) -self:UnHandleEvent(EVENTS.RemoveUnit) -if self.isFlightgroup then -self:UnHandleEvent(EVENTS.EngineStartup) -self:UnHandleEvent(EVENTS.Takeoff) -self:UnHandleEvent(EVENTS.Land) -self:UnHandleEvent(EVENTS.EngineShutdown) -self:UnHandleEvent(EVENTS.PilotDead) -self:UnHandleEvent(EVENTS.Ejection) -self:UnHandleEvent(EVENTS.Crash) -self.currbase=nil -elseif self.isArmygroup then -self:UnHandleEvent(EVENTS.Hit) -end -for _,_mission in pairs(self.missionqueue)do -local mission=_mission -self:MissionCancel(mission) -end -self.timerCheckZone:Stop() -self.timerQueueUpdate:Stop() -self.timerStatus:Stop() -self.CallScheduler:Clear() -if self.Scheduler then -self.Scheduler:Clear() -end -if self.flightcontrol then -for _,_element in pairs(self.elements)do -local element=_element -if element.parking then -self.flightcontrol:SetParkingFree(element.parking) -end -end -self.flightcontrol:_RemoveFlight(self) -end -if self:IsAlive()and not(self:IsDead()or self:IsStopped())then -local life,life0=self:GetLifePoints() -local state=self:GetState() -local text=string.format("WARNING: Group is still alive! Current state=%s. Life points=%d/%d. Use OPSGROUP:Destroy() or OPSGROUP:Despawn() for a clean stop",state,life,life0) -self:T(self.lid..text) -end -_DATABASE.FLIGHTGROUPS[self.groupname]=nil -self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") -end -function OPSGROUP:onafterOutOfAmmo(From,Event,To) -self:T(self.lid..string.format("Group is out of ammo at t=%.3f",timer.getTime())) -end -function OPSGROUP:_CheckCargoTransport() -local Time=timer.getAbsTime() -if self.verbose>=1 then -local text="" -for _,_element in pairs(self.elements)do -local element=_element -for _,_cargo in pairs(element.cargoBay)do -local cargo=_cargo -if cargo.group then -text=text..string.format("\n- %s in carrier %s, reserved=%s",tostring(cargo.group:GetName()),tostring(element.name),tostring(cargo.reserved)) -else -text=text..string.format("\n- storage %s=%d kg in carrier %s [UID=%s]", -tostring(cargo.storageType),tostring(cargo.storageAmount*cargo.storageWeight),tostring(element.name),tostring(cargo.cargoUID)) -end -end -end -if text==""then -text=" empty" -end -self:T(self.lid.."Cargo bay:"..text) -end -if self.verbose>=3 then -local text="" -for i,_transport in pairs(self.cargoqueue)do -local transport=_transport -local pickupzone=transport:GetPickupZone() -local deployzone=transport:GetDeployZone() -local pickupname=pickupzone and pickupzone:GetName()or"unknown" -local deployname=deployzone and deployzone:GetName()or"unknown" -text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s",i,transport.uid,transport:GetState(),pickupname,deployname) -for j,_cargo in pairs(transport:GetCargos())do -local cargo=_cargo -if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then -local state=cargo.opsgroup:GetState() -local status=cargo.opsgroup.cargoStatus -local name=cargo.opsgroup.groupname -local carriergroup,carrierelement,reserved=cargo.opsgroup:_GetMyCarrier() -local carrierGroupname=carriergroup and carriergroup.groupname or"none" -local carrierElementname=carrierelement and carrierelement.name or"none" -text=text..string.format("\n (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s",j,name,state,status,carrierGroupname,carrierElementname,tostring(cargo.delivered)) -else -end -end -end -if text~=""then -self:T(self.lid.."Cargo queue:"..text) -end -end -if self.cargoTransport and self.cargoTransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.DELIVERED then -self:DelOpsTransport(self.cargoTransport) -self.cargoTransport=nil -self.cargoTZC=nil -end -local mission=self:GetMissionCurrent() -if(not self.cargoTransport)and(mission==nil or mission.type==AUFTRAG.Type.NOTHING)then -self.cargoTransport=self:_GetNextCargoTransport() -if self.cargoTransport and mission then -self:MissionCancel(mission) -end -if self.cargoTransport and not self:IsActive()then -self:Activate() -end -end -if self.cargoTransport then -if self:IsNotCarrier()then -self.Tpickingup=nil -self.Tloading=nil -self.Ttransporting=nil -self.Tunloading=nil -self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self) -if self.cargoTZC then -self:T(self.lid..string.format("Not carrier ==> pickup at %s [TZC UID=%d]",self.cargoTZC.PickupZone and self.cargoTZC.PickupZone:GetName()or"unknown",self.cargoTZC.uid)) -self:__Pickup(-1) -else -self:T2(self.lid.."Not carrier ==> No TZC found") -end -elseif self:IsPickingup()then -self.Tpickingup=self.Tpickingup or Time -local tpickingup=Time-self.Tpickingup -self:T(self.lid..string.format("Picking up at %s [TZC UID=%d] for %s sec...",self.cargoTZC.PickupZone and self.cargoTZC.PickupZone:GetName()or"unknown",self.cargoTZC.uid,tpickingup)) -elseif self:IsLoading()then -self.Tloading=self.Tloading or Time -local tloading=Time-self.Tloading -self:T(self.lid..string.format("Loading at %s [TZC UID=%d] for %.1f sec...",self.cargoTZC.PickupZone and self.cargoTZC.PickupZone:GetName()or"unknown",self.cargoTZC.uid,tloading)) -local boarding=false -local gotcargo=false -for _,_cargo in pairs(self.cargoTZC.Cargos)do -local cargo=_cargo -if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then -if cargo.opsgroup and cargo.opsgroup:IsBoarding(self.groupname)then -boarding=true -end -if cargo.opsgroup and cargo.opsgroup:IsLoaded(self.groupname)then -gotcargo=true -end -else -local mycargo=self:_GetMyCargoBayFromUID(cargo.uid) -if mycargo and mycargo.storageAmount>0 then -gotcargo=true -end -end -end -if gotcargo and self.cargoTransport:_CheckRequiredCargos(self.cargoTZC,self)and not boarding then -self:T(self.lid.."Boarding/loading finished ==> Loaded") -self.Tloading=nil -self:LoadingDone() -else -self:Loading() -end -elseif self:IsTransporting()then -self.Ttransporting=self.Ttransporting or Time -local ttransporting=Time-self.Ttransporting -self:T(self.lid.."Transporting (nothing to do)") -elseif self:IsUnloading()then -self.Tunloading=self.Tunloading or Time -local tunloading=Time-self.Tunloading -self:T(self.lid.."Unloading ==> Checking if all cargo was delivered") -local delivered=true -for _,_cargo in pairs(self.cargoTZC.Cargos)do -local cargo=_cargo -if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then -local carrierGroup=cargo.opsgroup:_GetMyCarrierGroup() -if(carrierGroup and carrierGroup:GetName()==self:GetName())and not cargo.delivered then -delivered=false -break -end -else -local mycargo=self:_GetMyCargoBayFromUID(cargo.uid) -if mycargo and not cargo.delivered then -delivered=false -break -end -end -end -if delivered then -self:T(self.lid.."Unloading finished ==> UnloadingDone") -self:UnloadingDone() -else -self:Unloading() -end -end -if self.verbose>=2 and self.cargoTransport then -local pickupzone=self.cargoTransport:GetPickupZone(self.cargoTZC) -local deployzone=self.cargoTransport:GetDeployZone(self.cargoTZC) -local pickupname=pickupzone and pickupzone:GetName()or"unknown" -local deployname=deployzone and deployzone:GetName()or"unknown" -local text=string.format("Carrier [%s]: %s --> %s",self.carrierStatus,pickupname,deployname) -for _,_cargo in pairs(self.cargoTransport:GetCargos(self.cargoTZC))do -local cargo=_cargo -if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then -local name=cargo.opsgroup:GetName() -local gstatus=cargo.opsgroup:GetState() -local cstatus=cargo.opsgroup.cargoStatus -local weight=cargo.opsgroup:GetWeightTotal() -local carriergroup,carrierelement,reserved=cargo.opsgroup:_GetMyCarrier() -local carrierGroupname=carriergroup and carriergroup.groupname or"none" -local carrierElementname=carrierelement and carrierelement.name or"none" -text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s",name,weight,gstatus,cstatus,carrierElementname,carrierGroupname,tostring(cargo.delivered)) -else -end -end -self:I(self.lid..text) -end -end -return self -end -function OPSGROUP:_IsInCargobay(OpsGroup) -for _,_element in pairs(self.elements)do -local element=_element -for _,_cargo in pairs(element.cargoBay)do -local cargo=_cargo -if cargo.group.groupname==OpsGroup.groupname then -return true -end -end -end -return false -end -function OPSGROUP:_AddCargobay(CargoGroup,CarrierElement,Reserved) -local cargo=self:_GetCargobay(CargoGroup) -if cargo then -cargo.reserved=Reserved -else -cargo={} -cargo.group=CargoGroup -cargo.reserved=Reserved -table.insert(CarrierElement.cargoBay,cargo) -end -CargoGroup:_SetMyCarrier(self,CarrierElement,Reserved) -self.cargoBay[CargoGroup.groupname]=CarrierElement.name -if not Reserved then -local weight=CargoGroup:GetWeightTotal() -self:AddWeightCargo(CarrierElement.name,weight) -end -return self -end -function OPSGROUP:_AddCargobayStorage(CarrierElement,CargoUID,StorageType,StorageAmount,StorageWeight) -local MyCargo=self:_CreateMyCargo(CargoUID,nil,StorageType,StorageAmount,StorageWeight) -self:_AddMyCargoBay(MyCargo,CarrierElement) -end -function OPSGROUP:_CreateMyCargo(CargoUID,OpsGroup,StorageType,StorageAmount,StorageWeight) -local cargo={} -cargo.cargoUID=CargoUID -cargo.group=OpsGroup -cargo.storageType=StorageType -cargo.storageAmount=StorageAmount -cargo.storageWeight=StorageWeight -cargo.reserved=false -return cargo -end -function OPSGROUP:_AddMyCargoBay(MyCargo,CarrierElement) -table.insert(CarrierElement.cargoBay,MyCargo) -if not MyCargo.reserved then -local weight=0 -if MyCargo.group then -weight=MyCargo.group:GetWeightTotal() -else -weight=MyCargo.storageAmount*MyCargo.storageWeight -end -self:AddWeightCargo(CarrierElement.name,weight) -end -end -function OPSGROUP:_GetMyCargoBayFromUID(uid) -for _,_element in pairs(self.elements)do -local element=_element -for i,_mycargo in pairs(element.cargoBay)do -local mycargo=_mycargo -if mycargo.cargoUID and mycargo.cargoUID==uid then -return mycargo,element,i -end -end -end -return nil,nil,nil -end -function OPSGROUP:GetCargoGroups(CarrierName) -local cargos={} -for _,_element in pairs(self.elements)do -local element=_element -if CarrierName==nil or element.name==CarrierName then -for _,_cargo in pairs(element.cargoBay)do -local cargo=_cargo -if not cargo.reserved then -table.insert(cargos,cargo.group) -end -end -end -end -return cargos -end -function OPSGROUP:_GetCargobay(CargoGroup) -local CarrierElement=nil -local cargobayIndex=nil -local reserved=nil -for i,_element in pairs(self.elements)do -local element=_element -for j,_cargo in pairs(element.cargoBay)do -local cargo=_cargo -if cargo.group and cargo.group.groupname==CargoGroup.groupname then -return cargo,j,element -end -end -end -return nil,nil,nil -end -function OPSGROUP:_GetCargobayElement(Element,CargoUID) -self:T3({Element=Element,CargoUID=CargoUID}) -for i,_mycargo in pairs(Element.cargoBay)do -local mycargo=_mycargo -if mycargo.cargoUID and mycargo.cargoUID==CargoUID then -return mycargo -end -end -return nil -end -function OPSGROUP:_DelCargobayElement(Element,MyCargo) -for i,_mycargo in pairs(Element.cargoBay)do -local mycargo=_mycargo -if mycargo.cargoUID and MyCargo.cargoUID and mycargo.cargoUID==MyCargo.cargoUID then -if MyCargo.group then -self:RedWeightCargo(Element.name,MyCargo.group:GetWeightTotal()) -else -self:RedWeightCargo(Element.name,MyCargo.storageAmount*MyCargo.storageWeight) -end -table.remove(Element.cargoBay,i) -return true -end -end -return false -end -function OPSGROUP:_DelCargobay(CargoGroup) -if self.cargoBay[CargoGroup.groupname]then -self.cargoBay[CargoGroup.groupname]=nil -end -local cargoBayItem,cargoBayIndex,CarrierElement=self:_GetCargobay(CargoGroup) -if cargoBayItem and cargoBayIndex then -self:T(self.lid..string.format("Removing cargo group %s from cargo bay (index=%d) of carrier %s",CargoGroup:GetName(),cargoBayIndex,CarrierElement.name)) -table.remove(CarrierElement.cargoBay,cargoBayIndex) -if not cargoBayItem.reserved then -local weight=CargoGroup:GetWeightTotal() -self:RedWeightCargo(CarrierElement.name,weight) -end -return true -end -self:T(self.lid.."ERROR: Group is not in cargo bay. Cannot remove it!") -return false -end -function OPSGROUP:_GetNextCargoTransport() -local coord=self:GetCoordinate() -local function _sort(a,b) -local transportA=a -local transportB=b -return(transportA.priomaxweight then -maxweight=weight -end -end -end -return maxweight -end -function OPSGROUP:GetWeightCargo(UnitName,IncludeReserved) -local weight=0 -for _,_element in pairs(self.elements)do -local element=_element -if(UnitName==nil or UnitName==element.name)and element.status~=OPSGROUP.ElementStatus.DEAD then -weight=weight+element.weightCargo or 0 -end -end -local gewicht=0 -for _,_element in pairs(self.elements)do -local element=_element -if(UnitName==nil or UnitName==element.name)and(element and element.status~=OPSGROUP.ElementStatus.DEAD)then -for _,_cargo in pairs(element.cargoBay)do -local cargo=_cargo -if(not cargo.reserved)or(cargo.reserved==true and(IncludeReserved==true or IncludeReserved==nil))then -if cargo.group then -gewicht=gewicht+cargo.group:GetWeightTotal() -else -gewicht=gewicht+cargo.storageAmount*cargo.storageWeight -end -end -end -end -end -self:T3(self.lid..string.format("Unit=%s (reserved=%s): weight=%d, gewicht=%d",tostring(UnitName),tostring(IncludeReserved),weight,gewicht)) -if IncludeReserved==false and gewicht~=weight then -self:T(self.lid..string.format("ERROR: FF weight!=gewicht: weight=%.1f, gewicht=%.1f",weight,gewicht)) -end -return gewicht -end -function OPSGROUP:GetWeightCargoMax(UnitName) -local weight=0 -for _,_element in pairs(self.elements)do -local element=_element -if(UnitName==nil or UnitName==element.name)and element.status~=OPSGROUP.ElementStatus.DEAD then -weight=weight+element.weightMaxCargo -end -end -return weight -end -function OPSGROUP:GetCargoOpsGroups() -local opsgroups={} -for _,_element in pairs(self.elements)do -local element=_element -for _,_cargo in pairs(element.cargoBay)do -local cargo=_cargo -table.insert(opsgroups,cargo.group) -end -end -return opsgroups -end -function OPSGROUP:AddWeightCargo(UnitName,Weight) -local element=self:GetElementByName(UnitName) -if element then -element.weightCargo=element.weightCargo+Weight -self:T(self.lid..string.format("%s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg",UnitName,Weight,element.weightCargo)) -if self.isFlightgroup then -trigger.action.setUnitInternalCargo(element.name,element.weightCargo) -end -end -return self -end -function OPSGROUP:RedWeightCargo(UnitName,Weight) -self:AddWeightCargo(UnitName,-Weight) -return self -end -function OPSGROUP:_GetWeightStorage(Storage,Total,Reserved,Amount) -local weight=Storage.cargoAmount -if not Total then -weight=weight-Storage.cargoLost-Storage.cargoLoaded-Storage.cargoDelivered -end -if Reserved then -weight=weight-Storage.cargoReserved -end -if not Amount then -weight=weight*Storage.cargoWeight -end -return weight -end -function OPSGROUP:CanCargo(Cargo) -if Cargo then -local weight=math.huge -if Cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then -local weight=Cargo.opsgroup:GetWeightTotal() -for _,_element in pairs(self.elements)do -local element=_element -if element and element.status~=OPSGROUP.ElementStatus.DEAD and element.weightMaxCargo>=weight then -return true -end -end -else -weight=Cargo.storage.cargoWeight -end -local bay=0 -for _,_element in pairs(self.elements)do -local element=_element -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -bay=bay+element.weightMaxCargo -end -end -if bay>=weight then -return true -end -end -return false -end -function OPSGROUP:FindCarrierForCargo(Weight) -for _,_element in pairs(self.elements)do -local element=_element -local free=self:GetFreeCargobay(element.name) -if free>=Weight then -return element -else -self:T3(self.lid..string.format("%s: Weight %d>%d free cargo bay",element.name,Weight,free)) -end -end -return nil -end -function OPSGROUP:_SetMyCarrier(CarrierGroup,CarrierElement,Reserved) -self:T(self.lid..string.format("Setting My Carrier: %s (%s), reserved=%s",CarrierGroup:GetName(),tostring(CarrierElement.name),tostring(Reserved))) -self.mycarrier.group=CarrierGroup -self.mycarrier.element=CarrierElement -self.mycarrier.reserved=Reserved -self.cargoTransportUID=CarrierGroup.cargoTransport and CarrierGroup.cargoTransport.uid or nil -end -function OPSGROUP:_GetMyCarrierGroup() -if self.mycarrier and self.mycarrier.group then -return self.mycarrier.group -end -return nil -end -function OPSGROUP:_GetMyCarrierElement() -if self.mycarrier and self.mycarrier.element then -return self.mycarrier.element -end -return nil -end -function OPSGROUP:_IsMyCarrierReserved() -if self.mycarrier then -return self.mycarrier.reserved -end -return nil -end -function OPSGROUP:_GetMyCarrier() -return self.mycarrier.group,self.mycarrier.element,self.mycarrier.reserved -end -function OPSGROUP:_RemoveMyCarrier() -self:T(self.lid..string.format("Removing my carrier!")) -self.mycarrier.group=nil -self.mycarrier.element=nil -self.mycarrier.reserved=nil -self.mycarrier={} -self.cargoTransportUID=nil -return self -end -function OPSGROUP:onafterPickup(From,Event,To) -local oldstatus=self.carrierStatus -self:_NewCarrierStatus(OPSGROUP.CarrierStatus.PICKUP) -local TZC=self.cargoTZC -local Zone=TZC.PickupZone -local inzone=self:IsInZone(Zone) -local airbasePickup=TZC.PickupAirbase -local ready4loading=false -if self:IsArmygroup()or self:IsNavygroup()then -ready4loading=inzone -else -ready4loading=self.currbase and airbasePickup and self.currbase:GetName()==airbasePickup:GetName()and self:IsParking() -if ready4loading==false and self.isHelo and self:IsLandedAt()and inzone then -ready4loading=true -end -end -if ready4loading then -if(self:IsArmygroup()or self:IsNavygroup())and not self:IsHolding()then -self:FullStop() -end -self:__Loading(-5) -else -local surfacetypes=nil -if self:IsArmygroup()or self:IsFlightgroup()then -surfacetypes={land.SurfaceType.LAND} -elseif self:IsNavygroup()then -surfacetypes={land.SurfaceType.WATER} -end -local Coordinate=Zone:GetRandomCoordinate(nil,nil,surfacetypes) -local uid=self:GetWaypointCurrentUID() -if self:IsFlightgroup()then -if self:IsParking()and self:IsUncontrolled()then -self:StartUncontrolled() -end -if airbasePickup then -local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC) -if path and oldstatus~=OPSGROUP.CarrierStatus.NOTCARRIER then -for i=#path.waypoints,1,-1 do -local wp=path.waypoints[i] -local coordinate=COORDINATE:NewFromWaypoint(wp) -local waypoint=FLIGHTGROUP.AddWaypoint(self,coordinate,nil,uid,nil,false);waypoint.temp=true -uid=waypoint.uid -if i==1 then -waypoint.temp=false -waypoint.detour=1 -end -end -else -local coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate,0.5) -local waypoint=FLIGHTGROUP.AddWaypoint(self,coordinate,nil,uid,UTILS.MetersToFeet(self.altitudeCruise),true);waypoint.detour=1 -end -elseif self.isHelo then -local waypoint=FLIGHTGROUP.AddWaypoint(self,Coordinate,nil,uid,UTILS.MetersToFeet(self.altitudeCruise),false);waypoint.detour=1 -else -self:T(self.lid.."ERROR: Transportcarrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") -end -if self.isHelo and self:IsLandedAt()then -local Task=self:GetTaskCurrent() -if Task then -self:TaskCancel(Task) -else -self:T(self.lid.."ERROR: No current task but landed at?!") -end -end -if self:IsWaiting()then -self:__Cruise(-2) -end -elseif self:IsNavygroup()then -local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC) -if path then -for i=#path.waypoints,1,-1 do -local wp=path.waypoints[i] -local coordinate=COORDINATE:NewFromWaypoint(wp) -local waypoint=NAVYGROUP.AddWaypoint(self,coordinate,nil,uid,nil,false);waypoint.temp=true -uid=waypoint.uid -end -end -local waypoint=NAVYGROUP.AddWaypoint(self,Coordinate,nil,uid,self.altitudeCruise,false);waypoint.detour=1 -self:__Cruise(-2) -elseif self:IsArmygroup()then -local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC) -local Formation=self.cargoTransport:_GetFormationPickup(self.cargoTZC,self) -if path and oldstatus~=OPSGROUP.CarrierStatus.NOTCARRIER then -for i=#path.waypoints,1,-1 do -local wp=path.waypoints[i] -local coordinate=COORDINATE:NewFromWaypoint(wp) -local waypoint=ARMYGROUP.AddWaypoint(self,coordinate,nil,uid,wp.action,false);waypoint.temp=true -uid=waypoint.uid -end -end -local waypoint=ARMYGROUP.AddWaypoint(self,Coordinate,nil,uid,Formation,false);waypoint.detour=1 -self:__Cruise(-2,nil,Formation) -end -end -end -function OPSGROUP:_SortCargo(Cargos) -local function _sort(a,b) -local cargoA=a -local cargoB=b -local weightA=0 -local weightB=0 -if cargoA.opsgroup then -weightA=cargoA.opsgroup:GetWeightTotal() -else -weightA=self:_GetWeightStorage(cargoA.storage) -end -if cargoB.opsgroup then -weightB=cargoB.opsgroup:GetWeightTotal() -else -weightB=self:_GetWeightStorage(cargoB.storage) -end -return weightA>weightB -end -table.sort(Cargos,_sort) -return Cargos -end -function OPSGROUP:onafterLoading(From,Event,To) -self:_NewCarrierStatus(OPSGROUP.CarrierStatus.LOADING) -local cargos={} -for _,_cargo in pairs(self.cargoTZC.Cargos)do -local cargo=_cargo -local canCargo=self:CanCargo(cargo) -local isCarrier=false -local isNotCargo=true -local isHolding=cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and(cargo.opsgroup:IsHolding()or cargo.opsgroup:IsLoaded())or true -local inZone=cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and(cargo.opsgroup:IsInZone(self.cargoTZC.EmbarkZone)or cargo.opsgroup:IsInUtero())or true -local isOnMission=cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsOnMission()or false -if isOnMission then -local mission=cargo.opsgroup:GetMissionCurrent() -if mission and((mission.opstransport and mission.opstransport.uid==self.cargoTransport.uid)or mission.type==AUFTRAG.Type.NOTHING)then -isOnMission=not isHolding -end -end -local isAvail=true -if cargo.type==OPSTRANSPORT.CargoType.STORAGE then -local nAvail=cargo.storage.storageFrom:GetAmount(cargo.storage.cargoType) -if nAvail>0 then -isAvail=true -else -isAvail=false -end -else -isCarrier=cargo.opsgroup:IsPickingup()or cargo.opsgroup:IsLoading()or cargo.opsgroup:IsTransporting()or cargo.opsgroup:IsUnloading() -isNotCargo=cargo.opsgroup:IsNotCargo(true) -end -local isDead=cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsDead()or false -self:T(self.lid..string.format("Loading: canCargo=%s, isCarrier=%s, isNotCargo=%s, isHolding=%s, isOnMission=%s", -tostring(canCargo),tostring(isCarrier),tostring(isNotCargo),tostring(isHolding),tostring(isOnMission))) -if canCargo and inZone and isNotCargo and isHolding and isAvail and(not(cargo.delivered or isDead or isCarrier or isOnMission))then -table.insert(cargos,cargo) -end -end -self:_SortCargo(cargos) -for _,_cargo in pairs(cargos)do -local cargo=_cargo -local weight=nil -if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then -weight=cargo.opsgroup:GetWeightTotal() -local carrier=self:FindCarrierForCargo(weight) -if carrier then -cargo.opsgroup:Board(self,carrier) -end -else -weight=self:_GetWeightStorage(cargo.storage,false) -local Amount=cargo.storage.storageFrom:GetAmount(cargo.storage.cargoType) -local Weight=Amount*cargo.storage.cargoWeight -weight=math.min(weight,Weight) -self:T(self.lid..string.format("Loading storage weight=%d kg (warehouse has %d kg)!",weight,Weight)) -for _,_element in pairs(self.elements)do -local element=_element -local free=self:GetFreeCargobay(element.name) -local w=math.min(weight,free) -if w>=cargo.storage.cargoWeight then -local amount=math.floor(w/cargo.storage.cargoWeight) -cargo.storage.storageFrom:RemoveAmount(cargo.storage.cargoType,amount) -cargo.storage.cargoLoaded=cargo.storage.cargoLoaded+amount -self:_AddCargobayStorage(element,cargo.uid,cargo.storage.cargoType,amount,cargo.storage.cargoWeight) -weight=weight-amount*cargo.storage.cargoWeight -local text=string.format("Element %s: loaded amount=%d (weight=%d) ==> left=%d kg",element.name,amount,amount*cargo.storage.cargoWeight,weight) -self:T(self.lid..text) -if weight<=0 then -break -end -end -end -end -end -end -function OPSGROUP:_NewCargoStatus(Status) -if self.verbose>=2 then -self:I(self.lid..string.format("New cargo status: %s --> %s",tostring(self.cargoStatus),tostring(Status))) -end -self.cargoStatus=Status -end -function OPSGROUP:_NewCarrierStatus(Status) -if self.verbose>=2 then -self:I(self.lid..string.format("New carrier status: %s --> %s",tostring(self.carrierStatus),tostring(Status))) -end -self.carrierStatus=Status -end -function OPSGROUP:_TransferCargo(CargoGroup,CarrierGroup,CarrierElement) -self:T(self.lid..string.format("Transferring cargo %s to new carrier group %s",CargoGroup:GetName(),CarrierGroup:GetName())) -self:Unload(CargoGroup) -CarrierGroup:Load(CargoGroup,CarrierElement) -end -function OPSGROUP:onafterLoad(From,Event,To,CargoGroup,Carrier) -self:T(self.lid..string.format("Loading group %s",tostring(CargoGroup.groupname))) -local carrier=Carrier or CargoGroup:_GetMyCarrierElement() -if not carrier then -local weight=CargoGroup:GetWeightTotal() -carrier=self:FindCarrierForCargo(weight) -end -if carrier then -CargoGroup:_NewCargoStatus(OPSGROUP.CargoStatus.LOADED) -CargoGroup:ClearWaypoints() -self:_AddCargobay(CargoGroup,carrier,false) -if CargoGroup:IsAlive()then -CargoGroup:Despawn(0,true) -end -CargoGroup:Embarked(self,carrier) -self:Loaded(CargoGroup) -if self.cargoTransport then -CargoGroup:_DelMyLift(self.cargoTransport) -self.cargoTransport:Loaded(CargoGroup,self,carrier) -else -self:T(self.lid..string.format("WARNING: Loaded cargo but no current OPSTRANSPORT assignment!")) -end -else -self:T(self.lid.."ERROR: Cargo has no carrier on Load event!") -end -end -function OPSGROUP:onafterLoadingDone(From,Event,To) -self:T(self.lid.."Carrier Loading Done ==> Transport") -self:__Transport(1) -end -function OPSGROUP:onbeforeTransport(From,Event,To) -if self.cargoTransport==nil then -return false -elseif self.cargoTransport:IsDelivered()then -return false -end -return true -end -function OPSGROUP:onafterTransport(From,Event,To) -self:_NewCarrierStatus(OPSGROUP.CarrierStatus.TRANSPORTING) -local Zone=self.cargoTZC.DeployZone -local inzone=self:IsInZone(Zone) -local airbaseDeploy=self.cargoTZC.DeployAirbase -local ready2deploy=false -if self:IsArmygroup()or self:IsNavygroup()then -ready2deploy=inzone -else -ready2deploy=self.currbase and airbaseDeploy and self.currbase:GetName()==airbaseDeploy:GetName()and self:IsParking() -if ready2deploy==false and(self.isHelo or self.isVTOL)and self:IsLandedAt()and inzone then -ready2deploy=true -end -end -if inzone then -if(self:IsArmygroup()or self:IsNavygroup())and not self:IsHolding()then -self:FullStop() -end -self:__Unloading(-5) -else -local surfacetypes=nil -if self:IsArmygroup()or self:IsFlightgroup()then -surfacetypes={land.SurfaceType.LAND} -elseif self:IsNavygroup()then -surfacetypes={land.SurfaceType.WATER,land.SurfaceType.SHALLOW_WATER} -end -local Coordinate=Zone:GetRandomCoordinate(nil,nil,surfacetypes) -local uid=self:GetWaypointCurrentUID() -if self:IsFlightgroup()then -if self:IsParking()and self:IsUncontrolled()then -self:StartUncontrolled() -end -if airbaseDeploy then -local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC) -if path then -for i=1,#path.waypoints do -local wp=path.waypoints[i] -local coordinate=COORDINATE:NewFromWaypoint(wp) -local waypoint=FLIGHTGROUP.AddWaypoint(self,coordinate,nil,uid,nil,false);waypoint.temp=true -uid=waypoint.uid -if i==#path.waypoints then -waypoint.temp=false -waypoint.detour=1 -end -end -else -local coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate,0.5) -local waypoint=FLIGHTGROUP.AddWaypoint(self,coordinate,nil,uid,UTILS.MetersToFeet(self.altitudeCruise),true);waypoint.detour=1 -end -elseif self.isHelo then -local waypoint=FLIGHTGROUP.AddWaypoint(self,Coordinate,nil,uid,UTILS.MetersToFeet(self.altitudeCruise),false);waypoint.detour=1 -else -self:T(self.lid.."ERROR: Aircraft (cargo carrier) cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") -end -if self.isHelo and self:IsLandedAt()then -local Task=self:GetTaskCurrent() -if Task then -self:TaskCancel(Task) -else -self:T(self.lid.."ERROR: No current task but landed at?!") -end -end -elseif self:IsArmygroup()then -local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC) -local Formation=self.cargoTransport:_GetFormationTransport(self.cargoTZC,self) -if path then -for i=1,#path.waypoints do -local wp=path.waypoints[i] -local coordinate=COORDINATE:NewFromWaypoint(wp) -local waypoint=ARMYGROUP.AddWaypoint(self,coordinate,nil,uid,wp.action,false);waypoint.temp=true -uid=waypoint.uid -end -end -local waypoint=ARMYGROUP.AddWaypoint(self,Coordinate,nil,uid,Formation,false);waypoint.detour=1 -self:Cruise(nil,Formation) -elseif self:IsNavygroup()then -local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC) -if path then -for i=1,#path.waypoints do -local wp=path.waypoints[i] -local coordinate=COORDINATE:NewFromWaypoint(wp) -local waypoint=NAVYGROUP.AddWaypoint(self,coordinate,nil,uid,nil,false);waypoint.temp=true -uid=waypoint.uid -end -end -local waypoint=NAVYGROUP.AddWaypoint(self,Coordinate,nil,uid,self.altitudeCruise,false);waypoint.detour=1 -self:Cruise() -end -end -end -function OPSGROUP:onafterUnloading(From,Event,To) -self:_NewCarrierStatus(OPSGROUP.CarrierStatus.UNLOADING) -self:T(self.lid.."Unloading..") -local zone=self.cargoTZC.DisembarkZone or self.cargoTZC.DeployZone -for _,_cargo in pairs(self.cargoTZC.Cargos)do -local cargo=_cargo -if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then -if cargo.opsgroup:IsLoaded(self.groupname)and not cargo.opsgroup:IsDead()then -local carrier=nil -local carrierGroup=nil -local disembarkToCarriers=cargo.disembarkCarriers~=nil or self.cargoTZC.disembarkToCarriers -if cargo.disembarkZone then -zone=cargo.disembarkZone -end -self:T(self.lid..string.format("Unloading cargo %s to zone %s",cargo.opsgroup:GetName(),zone and zone:GetName()or"No Zone Found!")) -if zone and zone:IsInstanceOf("ZONE_AIRBASE")and zone:GetAirbase():IsShip()then -local shipname=zone:GetAirbase():GetName() -local ship=UNIT:FindByName(shipname) -local group=ship:GetGroup() -carrierGroup=_DATABASE:GetOpsGroup(group:GetName()) -carrier=carrierGroup:GetElementByName(shipname) -end -if disembarkToCarriers then -self:T(self.lid..string.format("Trying to find disembark carriers in zone %s",zone:GetName())) -local disembarkCarriers=cargo.disembarkCarriers or self.cargoTZC.DisembarkCarriers -carrier,carrierGroup=self.cargoTransport:FindTransferCarrierForCargo(cargo.opsgroup,zone,disembarkCarriers,self.cargoTZC.DeployAirbase) -end -if(disembarkToCarriers and carrier and carrierGroup)or(not disembarkToCarriers)then -cargo.delivered=true -self.cargoTransport.Ndelivered=self.cargoTransport.Ndelivered+1 -if carrier and carrierGroup then -self:_TransferCargo(cargo.opsgroup,carrierGroup,carrier) -elseif zone and zone:IsInstanceOf("ZONE_AIRBASE")and zone:GetAirbase():IsShip()then -self:T(self.lid.."ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") -self:Unload(cargo.opsgroup) -else -if self.cargoTransport:GetDisembarkInUtero(self.cargoTZC)then -self:Unload(cargo.opsgroup) -else -local DisembarkZone=cargo.disembarkZone or self.cargoTransport:GetDisembarkZone(self.cargoTZC) -local Coordinate=nil -if DisembarkZone then -Coordinate=DisembarkZone:GetRandomCoordinate() -else -local element=cargo.opsgroup:_GetMyCarrierElement() -if element then -local zoneCarrier=self:GetElementZoneUnload(element.name) -Coordinate=zoneCarrier:GetRandomCoordinate() -else -self:E(self.lid..string.format("ERROR carrier element nil!")) -end -end -local Heading=math.random(0,359) -local activation=self.cargoTransport:GetDisembarkActivation(self.cargoTZC) -if cargo.disembarkActivation~=nil then -activation=cargo.disembarkActivation -end -self:Unload(cargo.opsgroup,Coordinate,activation,Heading) -end -self.cargoTransport:Unloaded(cargo.opsgroup,self) -end -else -self:T(self.lid.."Cargo needs carrier but no carrier is avaiable (yet)!") -end -else -end -else -if not cargo.delivered then -for _,_element in pairs(self.elements)do -local element=_element -local mycargo=self:_GetCargobayElement(element,cargo.uid) -if mycargo then -cargo.storage.storageTo:AddAmount(mycargo.storageType,mycargo.storageAmount) -cargo.storage.cargoDelivered=cargo.storage.cargoDelivered+mycargo.storageAmount -cargo.storage.cargoLoaded=cargo.storage.cargoLoaded-mycargo.storageAmount -self:_DelCargobayElement(element,mycargo) -self:T2(self.lid..string.format("Cargo loaded=%d, delivered=%d, lost=%d",cargo.storage.cargoLoaded,cargo.storage.cargoDelivered,cargo.storage.cargoLost)) -end -end -local amountToDeliver=self:_GetWeightStorage(cargo.storage,false,false,true) -local amountTotal=self:_GetWeightStorage(cargo.storage,true,false,true) -local text=string.format("Amount delivered=%d, total=%d",amountToDeliver,amountTotal) -self:T(self.lid..text) -if amountToDeliver<=0 then -cargo.delivered=true -self.cargoTransport.Ndelivered=self.cargoTransport.Ndelivered+1 -local text=string.format("Ndelivered=%d delivered=%s",self.cargoTransport.Ndelivered,tostring(cargo.delivered)) -self:T(self.lid..text) -end -end -end -end -end -function OPSGROUP:onbeforeUnload(From,Event,To,OpsGroup,Coordinate,Heading) -local removed=self:_DelCargobay(OpsGroup) -return removed -end -function OPSGROUP:onafterUnload(From,Event,To,OpsGroup,Coordinate,Activated,Heading) -OpsGroup:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) -if Coordinate then -local Template=UTILS.DeepCopy(OpsGroup.template) -if Activated==false then -Template.lateActivation=true -else -Template.lateActivation=false -end -for _,Unit in pairs(Template.units)do -local element=OpsGroup:GetElementByName(Unit.name) -if element then -local vec3=element.vec3 -local rvec2={x=Unit.x-Template.x,y=Unit.y-Template.y} -local cvec2={x=Coordinate.x,y=Coordinate.z} -Unit.x=cvec2.x+rvec2.x -Unit.y=cvec2.y+rvec2.y -Unit.alt=land.getHeight({x=Unit.x,y=Unit.y}) -Unit.heading=Heading and math.rad(Heading)or Unit.heading -Unit.psi=-Unit.heading -end -end -OpsGroup:_Respawn(0,Template) -if OpsGroup:IsNavygroup()then -OpsGroup:ClearWaypoints() -OpsGroup.currentwp=1 -OpsGroup.passedfinalwp=true -NAVYGROUP.AddWaypoint(OpsGroup,Coordinate,nil,nil,nil,false) -elseif OpsGroup:IsArmygroup()then -OpsGroup:ClearWaypoints() -OpsGroup.currentwp=1 -OpsGroup.passedfinalwp=true -ARMYGROUP.AddWaypoint(OpsGroup,Coordinate,nil,nil,nil,false) -end -else -OpsGroup.position=self:GetVec3() -end -OpsGroup:Disembarked(OpsGroup:_GetMyCarrierGroup(),OpsGroup:_GetMyCarrierElement()) -self:Unloaded(OpsGroup) -OpsGroup:_RemoveMyCarrier() -end -function OPSGROUP:onafterUnloaded(From,Event,To,OpsGroupCargo) -self:T(self.lid..string.format("Unloaded OPSGROUP %s",OpsGroupCargo:GetName())) -if OpsGroupCargo.legion and OpsGroupCargo:IsInZone(OpsGroupCargo.legion.spawnzone)then -self:T(self.lid..string.format("Unloaded group %s returned to legion",OpsGroupCargo:GetName())) -OpsGroupCargo:Returned() -end -local paused=OpsGroupCargo:_CountPausedMissions()>0 -if paused then -OpsGroupCargo:UnpauseMission() -end -end -function OPSGROUP:onafterUnloadingDone(From,Event,To) -self:T(self.lid.."Cargo unloading done..") -if self:IsFlightgroup()and self:IsLandedAt()then -local Task=self:GetTaskCurrent() -self:__TaskCancel(5,Task) -end -local delivered=self:_CheckGoPickup(self.cargoTransport) -if not delivered then -self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self) -if self.cargoTZC then -self:T(self.lid.."Unloaded: Still cargo left ==> Pickup") -self:Pickup() -else -self:T(self.lid..string.format("WARNING: Not all cargo was delivered but could not get a transport zone combo ==> setting carrier state to NOT CARRIER")) -self:_NewCarrierStatus(OPSGROUP.CarrierStatus.NOTCARRIER) -end -else -self:T(self.lid.."Unloaded: ALL cargo unloaded ==> Delivered (current)") -self:Delivered(self.cargoTransport) -end -end -function OPSGROUP:onafterDelivered(From,Event,To,CargoTransport) -if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then -if self:IsPickingup()then -local wpindex=self:GetWaypointIndexNext(false) -if wpindex then -self:RemoveWaypoint(wpindex) -end -self.isLandingAtAirbase=nil -elseif self:IsLoading()then -elseif self:IsTransporting()then -elseif self:IsUnloading()then -end -self:_NewCarrierStatus(OPSGROUP.CarrierStatus.NOTCARRIER) -if self:IsFlightgroup()then -local function atbase(_airbase) -local airbase=_airbase -if airbase and self.currbase then -if airbase.AirbaseName==self.currbase.AirbaseName then -return true -end -end -return false -end -if self:IsUncontrolled()and not atbase(self.destbase)then -self:StartUncontrolled() -end -if self:IsLandedAt()then -local Task=self:GetTaskCurrent() -self:TaskCancel(Task) -end -else -self:__Cruise(-0.1) -end -self.cargoTransport:SetCarrierTransportStatus(self,OPSTRANSPORT.Status.DELIVERED) -self:T(self.lid..string.format("All cargo of transport UID=%d delivered ==> check group done in 0.2 sec",self.cargoTransport.uid)) -self:_CheckGroupDone(0.2) -end -end -function OPSGROUP:onafterTransportCancel(From,Event,To,Transport) -if self.cargoTransport and self.cargoTransport.uid==Transport.uid then -self:T(self.lid..string.format("Cancel current transport %d",Transport.uid)) -local calldelivered=false -if self:IsPickingup()then -calldelivered=true -elseif self:IsLoading()then -local cargos=Transport:GetCargoOpsGroups(false) -for _,_opsgroup in pairs(cargos)do -local opsgroup=_opsgroup -if opsgroup:IsBoarding(self.groupname)then -opsgroup:RemoveWaypoint(self.currentwp+1) -self:_DelCargobay(opsgroup) -opsgroup:_RemoveMyCarrier() -opsgroup:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) -elseif opsgroup:IsLoaded(self.groupname)then -local zoneCarrier=self:GetElementZoneUnload(opsgroup:_GetMyCarrierElement().name) -local Coordinate=zoneCarrier and zoneCarrier:GetRandomCoordinate()or self.cargoTransport:GetEmbarkZone(self.cargoTZC):GetRandomCoordinate() -local Heading=math.random(0,359) -self:Unload(opsgroup,Coordinate,self.cargoTransport:GetDisembarkActivation(self.cargoTZC),Heading) -self.cargoTransport:Unloaded(opsgroup,self) -end -end -calldelivered=true -elseif self:IsTransporting()then -elseif self:IsUnloading()then -else -end -if calldelivered then -self:__Delivered(-2,Transport) -end -else -Transport:SetCarrierTransportStatus(self,AUFTRAG.GroupStatus.CANCELLED) -self:DelOpsTransport(Transport) -self:_CheckGroupDone(1) -end -end -function OPSGROUP:onbeforeBoard(From,Event,To,CarrierGroup,Carrier) -if self:IsDead()then -self:T(self.lid.."Group DEAD ==> Deny Board transition!") -return false -elseif CarrierGroup:IsDead()then -self:T(self.lid.."Carrier Group DEAD ==> Deny Board transition!") -self:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) -return false -elseif Carrier.status==OPSGROUP.ElementStatus.DEAD then -self:T(self.lid.."Carrier Element DEAD ==> Deny Board transition!") -self:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) -return false -end -return true -end -function OPSGROUP:onafterBoard(From,Event,To,CarrierGroup,Carrier) -local CarrierIsArmyOrNavy=CarrierGroup:IsArmygroup()or CarrierGroup:IsNavygroup() -local CargoIsArmyOrNavy=self:IsArmygroup()or self:IsNavygroup() -if(CarrierIsArmyOrNavy and(CarrierGroup:GetVelocity(Carrier.name)<=1))or(CarrierGroup:IsFlightgroup()and(CarrierGroup:IsParking()or CarrierGroup:IsLandedAt()))then -local board=self.speedMax>0 and CargoIsArmyOrNavy and self:IsAlive()and CarrierGroup:IsAlive() -if self:IsArmygroup()and CarrierGroup:IsNavygroup()then -board=false -end -if self:IsLoaded()then -self:T(self.lid..string.format("Group is loaded currently ==> Moving directly to new carrier - No Unload(), Disembart() events triggered!")) -self:_RemoveMyCarrier() -CarrierGroup:Load(self) -elseif board then -self:_NewCargoStatus(OPSGROUP.CargoStatus.BOARDING) -self:T(self.lid..string.format("Boarding group=%s [%s], carrier=%s",CarrierGroup:GetName(),CarrierGroup:GetState(),tostring(Carrier.name))) -local Coordinate=Carrier.unit:GetCoordinate() -self:ClearWaypoints(self.currentwp+1) -if self.isArmygroup then -local waypoint=ARMYGROUP.AddWaypoint(self,Coordinate,nil,nil,ENUMS.Formation.Vehicle.Diamond);waypoint.detour=1 -self:Cruise() -else -local waypoint=NAVYGROUP.AddWaypoint(self,Coordinate);waypoint.detour=1 -self:Cruise() -end -CarrierGroup:_AddCargobay(self,Carrier,true) -else -self:T(self.lid..string.format("Board [loaded=%s] with direct load to carrier group=%s, element=%s",tostring(self:IsLoaded()),CarrierGroup:GetName(),tostring(Carrier.name))) -local mycarriergroup=self:_GetMyCarrierGroup() -if mycarriergroup then -self:T(self.lid..string.format("Current carrier group %s",mycarriergroup:GetName())) -end -if mycarriergroup and mycarriergroup:GetName()~=CarrierGroup:GetName()then -self:T(self.lid.."Unloading from mycarrier") -mycarriergroup:Unload(self) -end -CarrierGroup:Load(self) -end -else -self:T(self.lid.."Carrier not ready for boarding yet ==> repeating boarding call in 10 sec") -self:__Board(-10,CarrierGroup,Carrier) -CarrierGroup:_AddCargobay(self,Carrier,true) -end -end -function OPSGROUP:_CheckInZones() -if self.checkzones and self:IsAlive()then -local Ncheck=self.checkzones:Count() -local Ninside=self.inzones:Count() -self:T(self.lid..string.format("Check if group is in %d zones. Currently it is in %d zones.",self.checkzones:Count(),self.inzones:Count())) -local leftzones={} -for inzonename,inzone in pairs(self.inzones:GetSet())do -local isstillinzone=self.group:IsInZone(inzone) -if not isstillinzone then -table.insert(leftzones,inzone) -end -end -for _,leftzone in pairs(leftzones)do -self:LeaveZone(leftzone) -end -local enterzones={} -for checkzonename,_checkzone in pairs(self.checkzones:GetSet())do -local checkzone=_checkzone -local isincheckzone=self.group:IsInZone(checkzone) -if isincheckzone and not self.inzones:_Find(checkzonename)then -table.insert(enterzones,checkzone) -end -end -for _,enterzone in pairs(enterzones)do -self:EnterZone(enterzone) -end -end -end -function OPSGROUP:_CheckDetectedUnits() -if self.detectionOn and self.group and not self:IsDead()then -local detectedtargets=self.group:GetDetectedTargets() -local detected={} -local groups={} -for DetectionObjectID,Detection in pairs(detectedtargets or{})do -local DetectedObject=Detection.object -if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then -local unit=UNIT:Find(DetectedObject) -if unit and unit:IsAlive()then -local unitname=unit:GetName() -table.insert(detected,unit) -self:DetectedUnit(unit) -local group=unit:GetGroup() -if group then -groups[group:GetName()]=group -end -end -end -end -for groupname,group in pairs(groups)do -self:DetectedGroup(group) -end -local lost={} -for _,_unit in pairs(self.detectedunits:GetSet())do -local unit=_unit -local gotit=false -for _,_du in pairs(detected)do -local du=_du -if unit:GetName()==du:GetName()then -gotit=true -end -end -if not gotit then -table.insert(lost,unit:GetName()) -self:DetectedUnitLost(unit) -end -end -self.detectedunits:RemoveUnitsByName(lost) -local lost={} -for _,_group in pairs(self.detectedgroups:GetSet())do -local group=_group -local gotit=false -for _,_du in pairs(groups)do -local du=_du -if group:GetName()==du:GetName()then -gotit=true -end -end -if not gotit then -table.insert(lost,group:GetName()) -self:DetectedGroupLost(group) -end -end -self.detectedgroups:RemoveGroupsByName(lost) -end -end -function OPSGROUP:_CheckGroupDone(delay) -local fsmstate=self:GetState() -if self:IsAlive()and self.isAI then -if delay and delay>0 then -self:T(self.lid..string.format("Check OPSGROUP done? [state=%s] in %.3f seconds...",fsmstate,delay)) -self:ScheduleOnce(delay,self._CheckGroupDone,self) -else -self:T(self.lid..string.format("Check OSGROUP done? [state=%s]",fsmstate)) -if self:IsEngaging()then -self:T(self.lid.."Engaging! Group NOT done ==> UpdateRoute()") -self:UpdateRoute() -return -end -if self:IsReturning()then -self:T(self.lid.."Returning! Group NOT done...") -return -end -if self:IsRearming()then -self:T(self.lid.."Rearming! Group NOT done...") -return -end -if self:IsRetreating()then -self:T(self.lid.."Retreating! Group NOT done...") -return -end -if self:IsBoarding()then -self:T(self.lid.."Boarding! Group NOT done...") -return -end -if self:IsWaiting()then -self:T(self.lid.."Waiting! Group NOT done...") -return -end -local nTasks=self:CountRemainingTasks() -local nMissions=self:CountRemainingMissison() -local nTransports=self:CountRemainingTransports() -local nPaused=self:_CountPausedMissions() -if nPaused>0 and nPaused==nMissions then -local missionpaused=self:_GetPausedMission() -self:T(self.lid..string.format("Found paused mission %s [%s]. Unpausing mission...",missionpaused.name,missionpaused.type)) -self:UnpauseMission() -return -end -if nTasks>0 or nMissions>0 or nTransports>0 then -self:T(self.lid..string.format("Group still has tasks, missions or transports ==> NOT DONE")) -return -end -local waypoint=self:GetWaypoint(self.currentwp) -if waypoint then -local ntasks=self:CountTasksWaypoint(waypoint.uid) -if ntasks>0 then -self:T(self.lid..string.format("Still got %d tasks for the current waypoint UID=%d ==> RETURN (no action)",ntasks,waypoint.uid)) -return -end -end -if self.adinfinitum then -if#self.waypoints>0 then -local i=self:GetWaypointIndexNext(true) -local speed=self:GetSpeedToWaypoint(i) -self:Cruise(speed) -self:T(self.lid..string.format("Adinfinitum=TRUE ==> Goto WP index=%d at speed=%d knots",i,speed)) -else -self:T(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop")) -self:__FullStop(-1) -end -else -if self:HasPassedFinalWaypoint()then -if self.legion and self.legionReturn then -self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE, LEGION set ==> RTZ")) -if self.isArmygroup then -self:T2(self.lid.."RTZ to legion spawn zone") -self:RTZ(self.legion.spawnzone) -elseif self.isNavygroup then -self:T2(self.lid.."RTZ to legion port zone") -self:RTZ(self.legion.portzone) -end -else -self:__FullStop(-1) -self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE ==> Full Stop")) -end -else -if#self.waypoints>0 then -self:T(self.lid..string.format("NOT Passed final WP, #WP>0 ==> Update Route")) -self:Cruise() -else -self:T(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop")) -self:__FullStop(-1) -end -end -end -end -end -end -function OPSGROUP:_CheckStuck() -if self:IsHolding()or self:Is("Rearming")or self:IsWaiting()or self:HasPassedFinalWaypoint()then -return -end -local Tnow=timer.getTime() -local ExpectedSpeed=self:GetExpectedSpeed() -local speed=self:GetVelocity() -if speed<0.1 then -if ExpectedSpeed>0 and not self.stuckTimestamp then -self:T2(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected",speed,ExpectedSpeed)) -self.stuckTimestamp=Tnow -self.stuckVec3=self:GetVec3() -end -else -self.stuckTimestamp=nil -end -if self.stuckTimestamp then -local holdtime=Tnow-self.stuckTimestamp -if holdtime>=5*60 and holdtime<10*60 then -self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime)) -if self:IsEngaging()then -self:__Disengage(1) -elseif self:IsReturning()then -self:T2(self.lid.."RTZ because of stuck") -self:__RTZ(1) -else -self:__Cruise(1) -end -elseif holdtime>=10*60 and holdtime<30*60 then -self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime)) -local mission=self:GetMissionCurrent() -if mission then -self:T(self.lid..string.format("WARNING: Cancelling mission %s [%s] due to being stuck",mission:GetName(),mission:GetType())) -self:MissionCancel(mission) -else -if self:IsReturning()then -self:T2(self.lid.."RTZ because of stuck") -self:__RTZ(1) -else -self:__Cruise(1) -end -end -elseif holdtime>=30*60 then -self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime)) -if self.legion then -self:T(self.lid..string.format("Asset is returned to its legion after being stuck!")) -self:ReturnToLegion() -end -end -end -end -function OPSGROUP:_CheckDamage() -self:T(self.lid..string.format("Checking damage...")) -self.life=0 -local damaged=false -for _,_element in pairs(self.elements)do -local element=_element -if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then -local life=element.unit:GetLife() -self.life=self.life+life -if life0 then -local ammo=self:GetAmmoTot() -if self:IsRearming()then -if ammo.Total>=self.ammo.Total then -self:Rearmed() -end -end -if self.outofAmmo and ammo.Total>0 then -self.outofAmmo=false -end -if ammo.Total==0 and not self.outofAmmo then -self.outofAmmo=true -self:OutOfAmmo() -end -if self.outofGuns and ammo.Guns>0 then -self.outofGuns=false -end -if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then -self.outofGuns=true -self:OutOfGuns() -end -if self.outofRockets and ammo.Rockets>0 then -self.outofRockets=false -end -if ammo.Rockets==0 and self.ammo.Rockets>0 and not self.outofRockets then -self.outofRockets=true -self:OutOfRockets() -end -if self.outofBombs and ammo.Bombs>0 then -self.outofBombs=false -end -if ammo.Bombs==0 and self.ammo.Bombs>0 and not self.outofBombs then -self.outofBombs=true -self:OutOfBombs() -end -if self.outofMissiles and ammo.Missiles>0 then -self.outofMissiles=false -end -if ammo.Missiles==0 and self.ammo.Missiles>0 and not self.outofMissiles then -self.outofMissiles=true -self:OutOfMissiles() -end -if self.outofMissilesAA and ammo.MissilesAA>0 then -self.outofMissilesAA=false -end -if ammo.MissilesAA==0 and self.ammo.MissilesAA>0 and not self.outofMissilesAA then -self.outofMissilesAA=true -self:OutOfMissilesAA() -end -if self.outofMissilesAG and ammo.MissilesAG>0 then -self.outofMissilesAG=false -end -if ammo.MissilesAG==0 and self.ammo.MissilesAG>0 and not self.outofMissilesAG then -self.outofMissilesAG=true -self:OutOfMissilesAG() -end -if self.outofMissilesAS and ammo.MissilesAS>0 then -self.outofMissilesAS=false -end -if ammo.MissilesAS==0 and self.ammo.MissilesAS>0 and not self.outofMissilesAS then -self.outofMissilesAS=true -self:OutOfMissilesAS() -end -if self.outofTorpedos and ammo.Torpedos>0 then -self.outofTorpedos=false -end -if ammo.Torpedos==0 and self.ammo.Torpedos>0 and not self.outofTorpedos then -self.outofTorpedos=true -self:OutOfTorpedos() -end -if self:IsEngaging()and ammo.Total==0 then -self:Disengage() -end -end -end -function OPSGROUP:_PrintTaskAndMissionStatus() -if self.verbose>=3 and#self.taskqueue>0 then -local text=string.format("Tasks #%d",#self.taskqueue) -for i,_task in pairs(self.taskqueue)do -local task=_task -local name=task.description -local taskid=task.dcstask.id or"unknown" -local status=task.status -local clock=UTILS.SecondsToClock(task.time,true) -local eta=task.time-timer.getAbsTime() -local started=task.timestamp and UTILS.SecondsToClock(task.timestamp,true)or"N/A" -local duration=-1 -if task.duration then -duration=task.duration -if task.timestamp then -duration=task.duration-(timer.getAbsTime()-task.timestamp) -else -duration=task.duration -end -end -if task.type==OPSGROUP.TaskType.SCHEDULED then -text=text..string.format("\n[%d] %s (%s): status=%s, scheduled=%s (%d sec), started=%s, duration=%d",i,taskid,name,status,clock,eta,started,duration) -elseif task.type==OPSGROUP.TaskType.WAYPOINT then -text=text..string.format("\n[%d] %s (%s): status=%s, waypoint=%d, started=%s, duration=%d, stopflag=%d",i,taskid,name,status,task.waypoint,started,duration,task.stopflag:Get()) -end -end -self:I(self.lid..text) -end -if self.verbose>=2 then -local Mission=self:GetMissionByID(self.currentmission) -local text=string.format("Missions %d, Current: %s",self:CountRemainingMissison(),Mission and Mission.name or"none") -for i,_mission in pairs(self.missionqueue)do -local mission=_mission -local Cstart=UTILS.SecondsToClock(mission.Tstart,true) -local Cstop=mission.Tstop and UTILS.SecondsToClock(mission.Tstop,true)or"INF" -text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d wp=%s targets=%d", -i,tostring(mission.name),mission.type,mission:GetGroupStatus(self),tostring(mission.status),Cstart,Cstop,mission.prio,tostring(mission:GetGroupWaypointIndex(self)),mission:CountMissionTargets()) -end -self:I(self.lid..text) -end -end -function OPSGROUP:_SimpleTaskFunction(Function,uid) -local DCSScript={} -DCSScript[#DCSScript+1]=string.format('local mygroup = _DATABASE:FindOpsGroup(\"%s\") ',self.groupname) -DCSScript[#DCSScript+1]=string.format('%s(mygroup, %d)',Function,uid) -local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript))) -return DCSTask -end -function OPSGROUP:_CreateWaypoint(waypoint) -waypoint.uid=self.wpcounter -waypoint.npassed=0 -waypoint.coordinate=COORDINATE:New(waypoint.x,waypoint.alt,waypoint.y) -waypoint.name=string.format("Waypoint UID=%d",waypoint.uid) -waypoint.patrol=false -waypoint.detour=false -waypoint.astar=false -waypoint.temp=false -local taskswp={} -local TaskPassingWaypoint=self:_SimpleTaskFunction("OPSGROUP._PassingWaypoint",waypoint.uid) -table.insert(taskswp,TaskPassingWaypoint) -waypoint.task=self.group:TaskCombo(taskswp) -self.wpcounter=self.wpcounter+1 -return waypoint -end -function OPSGROUP:_AddWaypoint(waypoint,wpnumber) -wpnumber=wpnumber or#self.waypoints+1 -table.insert(self.waypoints,wpnumber,waypoint) -self:T(self.lid..string.format("Adding waypoint at index=%d with UID=%d",wpnumber,waypoint.uid)) -if self.currentwp and wpnumber>self.currentwp then -self:_PassedFinalWaypoint(false,string.format("_AddWaypoint: wpnumber/index %d>%d self.currentwp",wpnumber,self.currentwp)) -end -end -function OPSGROUP:_InitWaypoints(WpIndexMin,WpIndexMax) -self.waypoints0=UTILS.DeepCopy(_DATABASE:GetGroupTemplate(self.groupname).route.points) -self.waypoints={} -WpIndexMin=WpIndexMin or 1 -WpIndexMax=WpIndexMax or#self.waypoints0 -WpIndexMax=math.min(WpIndexMax,#self.waypoints0) -for i=WpIndexMin,WpIndexMax do -local wp=self.waypoints0[i] -local Coordinate=COORDINATE:NewFromWaypoint(wp) -wp.speed=wp.speed or 0 -local speedknots=UTILS.MpsToKnots(wp.speed) -if i<=2 then -self.speedWp=wp.speed -end -local Speed=UTILS.MpsToKnots(wp.speed) -local Waypoint=nil -if self:IsFlightgroup()then -Waypoint=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,nil,Altitude,false) -elseif self:IsArmygroup()then -Waypoint=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,nil,wp.action,false) -elseif self:IsNavygroup()then -Waypoint=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,nil,Depth,false) -end -local DCStasks=wp.task and wp.task.params.tasks or nil -if DCStasks and self.useMEtasks then -for _,DCStask in pairs(DCStasks)do -if DCStask.id and DCStask.id~="WrappedAction"then -self:AddTaskWaypoint(DCStask,Waypoint,"ME Task") -end -end -end -end -self:T(self.lid..string.format("Initializing %d waypoints",#self.waypoints)) -if self:IsFlightgroup()then -self.homebase=self.homebase or self:GetHomebaseFromWaypoints() -local destbase=self:GetDestinationFromWaypoints() -self.destbase=self.destbase or destbase -self.currbase=self:GetHomebaseFromWaypoints() -if destbase and#self.waypoints>1 then -table.remove(self.waypoints,#self.waypoints) -end -if self.destbase==nil then -self.destbase=self.homebase -end -end -if#self.waypoints>0 then -if#self.waypoints==1 then -self:_PassedFinalWaypoint(true,"_InitWaypoints: #self.waypoints==1") -end -else -self:T(self.lid.."WARNING: No waypoints initialized. Number of waypoints is 0!") -end -return self -end -function OPSGROUP:Route(waypoints,delay) -if delay and delay>0 then -self:ScheduleOnce(delay,OPSGROUP.Route,self,waypoints) -else -if self:IsAlive()then -local DCSTask={ -id='Mission', -params={ -airborne=self:IsFlightgroup(), -route={points=waypoints}, -}, -} -self:SetTask(DCSTask) -else -self:T(self.lid.."ERROR: Group is not alive! Cannot route group.") -end -end -return self -end -function OPSGROUP:_UpdateWaypointTasks(n) -local waypoints=self.waypoints or{} -local nwaypoints=#waypoints -for i,_wp in pairs(waypoints)do -local wp=_wp -if i>=n or nwaypoints==1 then -self:T2(self.lid..string.format("Updating waypoint task for waypoint %d/%d ID=%d. Last waypoint passed %d",i,nwaypoints,wp.uid,self.currentwp)) -local taskswp={} -local TaskPassingWaypoint=self.group:TaskFunction("OPSGROUP._PassingWaypoint",self,wp.uid) -table.insert(taskswp,TaskPassingWaypoint) -wp.task=self.group:TaskCombo(taskswp) -end -end -end -function OPSGROUP._PassingWaypoint(opsgroup,uid) -local text=string.format("Group passing waypoint uid=%d",uid) -opsgroup:T(opsgroup.lid..text) -local waypoint=opsgroup:GetWaypointByID(uid) -if waypoint then -waypoint.npassed=waypoint.npassed+1 -local currentwp=opsgroup.currentwp -opsgroup.currentwp=opsgroup:GetWaypointIndex(uid) -local wpistemp=waypoint.temp or waypoint.detour or waypoint.astar -if wpistemp then -opsgroup:RemoveWaypointByID(uid) -end -local wpnext=opsgroup:GetWaypointNext() -if wpnext then -opsgroup:T(opsgroup.lid..string.format("Next waypoint UID=%d index=%d",wpnext.uid,opsgroup:GetWaypointIndex(wpnext.uid))) -if opsgroup.isArmygroup then -opsgroup.option.Formation=wpnext.action -end -opsgroup.speed=wpnext.speed -if opsgroup.speed<0.01 then -opsgroup.speed=UTILS.KmphToMps(opsgroup.speedCruise) -end -else -opsgroup:_PassedFinalWaypoint(true,"_PassingWaypoint No next Waypoint found") -end -if opsgroup.currentwp==#opsgroup.waypoints and not(opsgroup.adinfinitum or wpistemp)then -opsgroup:_PassedFinalWaypoint(true,"_PassingWaypoint currentwp==#waypoints and NOT adinfinitum and NOT a temporary waypoint") -end -if waypoint.temp then -if(opsgroup:IsNavygroup()or opsgroup:IsArmygroup())and opsgroup.currentwp==#opsgroup.waypoints then -opsgroup:Cruise() -end -elseif waypoint.astar then -opsgroup:Cruise() -elseif waypoint.detour then -if opsgroup:IsRearming()then -opsgroup:Rearming() -elseif opsgroup:IsRetreating()then -opsgroup:Retreated() -elseif opsgroup:IsReturning()then -opsgroup:Returned() -elseif opsgroup:IsPickingup()then -if opsgroup:IsFlightgroup()then -if opsgroup.cargoTZC then -if opsgroup.cargoTZC.PickupAirbase then -opsgroup:LandAtAirbase(opsgroup.cargoTZC.PickupAirbase) -else -local coordinate=opsgroup.cargoTZC.PickupZone:GetRandomCoordinate(nil,nil,{land.SurfaceType.LAND}) -opsgroup:LandAt(coordinate,60*60) -end -else -local coordinate=opsgroup:GetCoordinate() -opsgroup:LandAt(coordinate,60*60) -end -else -opsgroup:FullStop() -opsgroup:__Loading(-5) -end -elseif opsgroup:IsTransporting()then -if opsgroup:IsFlightgroup()then -if opsgroup.cargoTZC then -if opsgroup.cargoTZC.DeployAirbase then -opsgroup:LandAtAirbase(opsgroup.cargoTZC.DeployAirbase) -else -local coordinate=opsgroup.cargoTZC.DeployZone:GetRandomCoordinate(nil,nil,{land.SurfaceType.LAND}) -opsgroup:LandAt(coordinate,60*60) -end -else -local coordinate=opsgroup:GetCoordinate() -opsgroup:LandAt(coordinate,60*60) -end -else -opsgroup:FullStop() -opsgroup:Unloading() -end -elseif opsgroup:IsBoarding()then -local carrierGroup=opsgroup:_GetMyCarrierGroup() -local carrier=opsgroup:_GetMyCarrierElement() -if carrierGroup and carrierGroup:IsAlive()then -if carrier and carrier.unit and carrier.unit:IsAlive()then -carrierGroup:Load(opsgroup) -else -opsgroup:E(opsgroup.lid.."ERROR: Group cannot board assigned carrier UNIT as it is NOT alive!") -end -else -opsgroup:E(opsgroup.lid.."ERROR: Group cannot board assigned carrier GROUP as it is NOT alive!") -end -elseif opsgroup:IsEngaging()then -opsgroup:T(opsgroup.lid.."Passing engaging waypoint") -else -opsgroup:DetourReached() -if waypoint.detour==0 then -opsgroup:FullStop() -elseif waypoint.detour==1 then -opsgroup:Cruise() -else -opsgroup:E("ERROR: waypoint.detour should be 0 or 1") -opsgroup:FullStop() -end -end -else -if opsgroup.ispathfinding then -opsgroup.ispathfinding=false -end -opsgroup:PassingWaypoint(waypoint) -end -end -end -function OPSGROUP._TaskExecute(group,opsgroup,task) -local text=string.format("_TaskExecute %s",task.description) -opsgroup:T3(opsgroup.lid..text) -if opsgroup then -opsgroup:TaskExecute(task) -end -end -function OPSGROUP._TaskDone(group,opsgroup,task) -local text=string.format("_TaskDone %s",task.description) -opsgroup:T(opsgroup.lid..text) -if opsgroup then -opsgroup:TaskDone(task) -end -end -function OPSGROUP:SetDefaultROE(roe) -self.optionDefault.ROE=roe or ENUMS.ROE.ReturnFire -return self -end -function OPSGROUP:SwitchROE(roe) -if self:IsAlive()or self:IsInUtero()then -self.option.ROE=roe or self.optionDefault.ROE -if self:IsInUtero()then -self:T2(self.lid..string.format("Setting current ROE=%d when GROUP is SPAWNED",self.option.ROE)) -else -self.group:OptionROE(self.option.ROE) -self:T(self.lid..string.format("Setting current ROE=%d (%s)",self.option.ROE,self:_GetROEName(self.option.ROE))) -end -else -self:T(self.lid.."WARNING: Cannot switch ROE! Group is not alive") -end -return self -end -function OPSGROUP:_GetROEName(roe) -local name="unknown" -if roe==0 then -name="Weapon Free" -elseif roe==1 then -name="Open Fire/Weapon Free" -elseif roe==2 then -name="Open Fire" -elseif roe==3 then -name="Return Fire" -elseif roe==4 then -name="Weapon Hold" -end -return name -end -function OPSGROUP:GetROE() -return self.option.ROE or self.optionDefault.ROE -end -function OPSGROUP:SetDefaultROT(rot) -self.optionDefault.ROT=rot or ENUMS.ROT.PassiveDefense -return self -end -function OPSGROUP:SwitchROT(rot) -if self:IsFlightgroup()then -if self:IsAlive()or self:IsInUtero()then -self.option.ROT=rot or self.optionDefault.ROT -if self:IsInUtero()then -self:T2(self.lid..string.format("Setting current ROT=%d when GROUP is SPAWNED",self.option.ROT)) -else -self.group:OptionROT(self.option.ROT) -self:T(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)",self.option.ROT)) -end -else -self:T(self.lid.."WARNING: Cannot switch ROT! Group is not alive") -end -end -return self -end -function OPSGROUP:GetROT() -return self.option.ROT or self.optionDefault.ROT -end -function OPSGROUP:SetDefaultAlarmstate(alarmstate) -self.optionDefault.Alarm=alarmstate or 0 -return self -end -function OPSGROUP:SwitchAlarmstate(alarmstate) -if self:IsAlive()or self:IsInUtero()then -if self.isArmygroup or self.isNavygroup then -self.option.Alarm=alarmstate or self.optionDefault.Alarm -if self:IsInUtero()then -self:T2(self.lid..string.format("Setting current Alarm State=%d when GROUP is SPAWNED",self.option.Alarm)) -else -if self.option.Alarm==0 then -self.group:OptionAlarmStateAuto() -elseif self.option.Alarm==1 then -self.group:OptionAlarmStateGreen() -elseif self.option.Alarm==2 then -self.group:OptionAlarmStateRed() -else -self:T("ERROR: Unknown Alarm State! Setting to AUTO") -self.group:OptionAlarmStateAuto() -self.option.Alarm=0 -end -self:T(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)",self.option.Alarm)) -end -end -else -self:T(self.lid.."WARNING: Cannot switch Alarm State! Group is not alive.") -end -return self -end -function OPSGROUP:GetAlarmstate() -return self.option.Alarm or self.optionDefault.Alarm -end -function OPSGROUP:SetDefaultEPLRS(OnOffSwitch) -if OnOffSwitch==nil then -self.optionDefault.EPLRS=self.isEPLRS -else -self.optionDefault.EPLRS=OnOffSwitch -end -return self -end -function OPSGROUP:SwitchEPLRS(OnOffSwitch) -if self:IsAlive()or self:IsInUtero()then -if OnOffSwitch==nil then -self.option.EPLRS=self.optionDefault.EPLRS -else -self.option.EPLRS=OnOffSwitch -end -if self:IsInUtero()then -self:T2(self.lid..string.format("Setting current EPLRS=%s when GROUP is SPAWNED",tostring(self.option.EPLRS))) -else -self.group:CommandEPLRS(self.option.EPLRS) -self:T(self.lid..string.format("Setting current EPLRS=%s",tostring(self.option.EPLRS))) -end -else -self:E(self.lid.."WARNING: Cannot switch EPLRS! Group is not alive") -end -return self -end -function OPSGROUP:GetEPLRS() -return self.option.EPLRS or self.optionDefault.EPLRS -end -function OPSGROUP:SetDefaultEmission(OnOffSwitch) -if OnOffSwitch==nil then -self.optionDefault.Emission=true -else -self.optionDefault.EPLRS=OnOffSwitch -end -return self -end -function OPSGROUP:SwitchEmission(OnOffSwitch) -if self:IsAlive()or self:IsInUtero()then -if OnOffSwitch==nil then -self.option.Emission=self.optionDefault.Emission -else -self.option.Emission=OnOffSwitch -end -if self:IsInUtero()then -self:T2(self.lid..string.format("Setting current EMISSION=%s when GROUP is SPAWNED",tostring(self.option.Emission))) -else -self.group:EnableEmission(self.option.Emission) -self:T(self.lid..string.format("Setting current EMISSION=%s",tostring(self.option.Emission))) -end -else -self:E(self.lid.."WARNING: Cannot switch Emission! Group is not alive") -end -return self -end -function OPSGROUP:GetEmission() -return self.option.Emission or self.optionDefault.Emission -end -function OPSGROUP:SetDefaultInvisible(OnOffSwitch) -if OnOffSwitch==nil then -self.optionDefault.Invisible=true -else -self.optionDefault.Invisible=OnOffSwitch -end -return self -end -function OPSGROUP:SwitchInvisible(OnOffSwitch) -if self:IsAlive()or self:IsInUtero()then -if OnOffSwitch==nil then -self.option.Invisible=self.optionDefault.Invisible -else -self.option.Invisible=OnOffSwitch -end -if self:IsInUtero()then -self:T2(self.lid..string.format("Setting current INVISIBLE=%s when GROUP is SPAWNED",tostring(self.option.Invisible))) -else -self.group:SetCommandInvisible(self.option.Invisible) -self:T(self.lid..string.format("Setting current INVISIBLE=%s",tostring(self.option.Invisible))) -end -else -self:E(self.lid.."WARNING: Cannot switch Invisible! Group is not alive") -end -return self -end -function OPSGROUP:SetDefaultImmortal(OnOffSwitch) -if OnOffSwitch==nil then -self.optionDefault.Immortal=true -else -self.optionDefault.Immortal=OnOffSwitch -end -return self -end -function OPSGROUP:SwitchImmortal(OnOffSwitch) -if self:IsAlive()or self:IsInUtero()then -if OnOffSwitch==nil then -self.option.Immortal=self.optionDefault.Immortal -else -self.option.Immortal=OnOffSwitch -end -if self:IsInUtero()then -self:T2(self.lid..string.format("Setting current IMMORTAL=%s when GROUP is SPAWNED",tostring(self.option.Immortal))) -else -self.group:SetCommandImmortal(self.option.Immortal) -self:T(self.lid..string.format("Setting current IMMORTAL=%s",tostring(self.option.Immortal))) -end -else -self:E(self.lid.."WARNING: Cannot switch Immortal! Group is not alive") -end -return self -end -function OPSGROUP:SetDefaultTACAN(Channel,Morse,UnitName,Band,OffSwitch) -self.tacanDefault={} -self.tacanDefault.Channel=Channel or 74 -self.tacanDefault.Morse=Morse or"XXX" -self.tacanDefault.BeaconName=UnitName -if self:IsFlightgroup()then -Band=Band or"Y" -else -Band=Band or"X" -end -self.tacanDefault.Band=Band -if OffSwitch then -self.tacanDefault.On=false -else -self.tacanDefault.On=true -end -return self -end -function OPSGROUP:_SwitchTACAN(Tacan) -if Tacan then -self:SwitchTACAN(Tacan.Channel,Tacan.Morse,Tacan.BeaconName,Tacan.Band) -else -if self.tacanDefault.On then -self:SwitchTACAN() -else -self:TurnOffTACAN() -end -end -end -function OPSGROUP:SwitchTACAN(Channel,Morse,UnitName,Band) -if self:IsInUtero()then -self:T(self.lid..string.format("Switching TACAN to DEFAULT when group is spawned")) -self:SetDefaultTACAN(Channel,Morse,UnitName,Band) -elseif self:IsAlive()then -Channel=Channel or self.tacanDefault.Channel -Morse=Morse or self.tacanDefault.Morse -Band=Band or self.tacanDefault.Band -UnitName=UnitName or self.tacanDefault.BeaconName -local unit=self:GetUnit(1) -if UnitName then -if type(UnitName)=="number"then -unit=self.group:GetUnit(UnitName) -else -unit=UNIT:FindByName(UnitName) -end -end -if not unit then -self:T(self.lid.."WARNING: Could not get TACAN unit. Trying first unit in the group") -unit=self:GetUnit(1) -end -if unit and unit:IsAlive()then -local UnitID=unit:GetID() -local Type=BEACON.Type.TACAN -local System=BEACON.System.TACAN -if self:IsFlightgroup()then -System=BEACON.System.TACAN_TANKER_Y -end -local Frequency=UTILS.TACANToFrequency(Channel,Band) -unit:CommandActivateBeacon(Type,System,Frequency,UnitID,Channel,Band,true,Morse,true) -self.tacan.Channel=Channel -self.tacan.Morse=Morse -self.tacan.Band=Band -self.tacan.BeaconName=unit:GetName() -self.tacan.BeaconUnit=unit -self.tacan.On=true -self:T(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s",self.tacan.Channel,self.tacan.Band,tostring(self.tacan.Morse),self.tacan.BeaconName)) -else -self:T(self.lid.."ERROR: Cound not set TACAN! Unit is not alive") -end -else -self:T(self.lid.."ERROR: Cound not set TACAN! Group is not alive and not in utero any more") -end -return self -end -function OPSGROUP:TurnOffTACAN() -if self.tacan.BeaconUnit and self.tacan.BeaconUnit:IsAlive()then -self.tacan.BeaconUnit:CommandDeactivateBeacon() -end -self:T(self.lid..string.format("Switching TACAN OFF")) -self.tacan.On=false -end -function OPSGROUP:GetTACAN() -return self.tacan.Channel,self.tacan.Morse,self.tacan.Band,self.tacan.On,self.tacan.BeaconName -end -function OPSGROUP:GetBeaconTACAN() -return self.tacan -end -function OPSGROUP:SetDefaultICLS(Channel,Morse,UnitName,OffSwitch) -self.iclsDefault={} -self.iclsDefault.Channel=Channel or 1 -self.iclsDefault.Morse=Morse or"XXX" -self.iclsDefault.BeaconName=UnitName -if OffSwitch then -self.iclsDefault.On=false -else -self.iclsDefault.On=true -end -return self -end -function OPSGROUP:_SwitchICLS(Icls) -if Icls then -self:SwitchICLS(Icls.Channel,Icls.Morse,Icls.BeaconName) -else -if self.iclsDefault.On then -self:SwitchICLS() -else -self:TurnOffICLS() -end -end -end -function OPSGROUP:SwitchICLS(Channel,Morse,UnitName) -if self:IsInUtero()then -self:SetDefaultICLS(Channel,Morse,UnitName) -self:T2(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s when GROUP is SPAWNED",self.iclsDefault.Channel,tostring(self.iclsDefault.Morse),tostring(self.iclsDefault.BeaconName))) -elseif self:IsAlive()then -Channel=Channel or self.iclsDefault.Channel -Morse=Morse or self.iclsDefault.Morse -local unit=self:GetUnit(1) -if UnitName then -if type(UnitName)=="number"then -unit=self:GetUnit(UnitName) -else -unit=UNIT:FindByName(UnitName) -end -end -if not unit then -self:T(self.lid.."WARNING: Could not get ICLS unit. Trying first unit in the group") -unit=self:GetUnit(1) -end -if unit and unit:IsAlive()then -local UnitID=unit:GetID() -unit:CommandActivateICLS(Channel,UnitID,Morse) -self.icls.Channel=Channel -self.icls.Morse=Morse -self.icls.Band=nil -self.icls.BeaconName=unit:GetName() -self.icls.BeaconUnit=unit -self.icls.On=true -self:T(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s",self.icls.Channel,tostring(self.icls.Morse),self.icls.BeaconName)) -else -self:T(self.lid.."ERROR: Cound not set ICLS! Unit is not alive.") -end -end -return self -end -function OPSGROUP:TurnOffICLS() -if self.icls.BeaconUnit and self.icls.BeaconUnit:IsAlive()then -self.icls.BeaconUnit:CommandDeactivateICLS() -end -self:T(self.lid..string.format("Switching ICLS OFF")) -self.icls.On=false -end -function OPSGROUP:SetDefaultRadio(Frequency,Modulation,OffSwitch) -self.radioDefault={} -self.radioDefault.Freq=Frequency or 251 -self.radioDefault.Modu=Modulation or radio.modulation.AM -if OffSwitch then -self.radioDefault.On=false -else -self.radioDefault.On=true -end -return self -end -function OPSGROUP:GetRadio() -return self.radio.Freq,self.radio.Modu,self.radio.On -end -function OPSGROUP:SwitchRadio(Frequency,Modulation) -if self:IsInUtero()then -self:SetDefaultRadio(Frequency,Modulation) -self:T2(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED",self.radioDefault.Freq,UTILS.GetModulationName(self.radioDefault.Modu))) -elseif self:IsAlive()then -Frequency=Frequency or self.radioDefault.Freq -Modulation=Modulation or self.radioDefault.Modu -if self:IsFlightgroup()and not self.radio.On then -self.group:SetOption(AI.Option.Air.id.SILENCE,false) -end -self.group:CommandSetFrequency(Frequency,Modulation) -self.radio.Freq=Frequency -self.radio.Modu=Modulation -self.radio.On=true -self:T(self.lid..string.format("Switching radio to frequency %.3f MHz %s",self.radio.Freq,UTILS.GetModulationName(self.radio.Modu))) -else -self:T(self.lid.."ERROR: Cound not set Radio! Group is not alive or not in utero any more") -end -return self -end -function OPSGROUP:TurnOffRadio() -if self:IsAlive()then -if self:IsFlightgroup()then -self.group:SetOption(AI.Option.Air.id.SILENCE,true) -self.radio.On=false -self:T(self.lid..string.format("Switching radio OFF")) -else -self:T(self.lid.."ERROR: Radio can only be turned off for aircraft!") -end -end -return self -end -function OPSGROUP:SetDefaultFormation(Formation) -self.optionDefault.Formation=Formation -return self -end -function OPSGROUP:SwitchFormation(Formation) -if self:IsAlive()then -Formation=Formation or self.optionDefault.Formation -if self:IsFlightgroup()then -self.group:SetOption(AI.Option.Air.id.FORMATION,Formation) -elseif self.isArmygroup then -else -self:T(self.lid.."ERROR: Formation can only be set for aircraft or ground units!") -return self -end -self.option.Formation=Formation -self:T(self.lid..string.format("Switching formation to %s",tostring(self.option.Formation))) -end -return self -end -function OPSGROUP:SetDefaultCallsign(CallsignName,CallsignNumber) -self:T(self.lid..string.format("Setting Default callsing %s-%s",tostring(CallsignName),tostring(CallsignNumber))) -self.callsignDefault={} -self.callsignDefault.NumberSquad=CallsignName -self.callsignDefault.NumberGroup=CallsignNumber or 1 -self.callsignDefault.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) -return self -end -function OPSGROUP:SwitchCallsign(CallsignName,CallsignNumber) -if self:IsInUtero()then -self:SetDefaultCallsign(CallsignName,CallsignNumber) -elseif self:IsAlive()then -CallsignName=CallsignName or self.callsignDefault.NumberSquad -CallsignNumber=CallsignNumber or self.callsignDefault.NumberGroup -self.callsign.NumberSquad=CallsignName -self.callsign.NumberGroup=CallsignNumber -self:T(self.lid..string.format("Switching callsign to %d-%d",self.callsign.NumberSquad,self.callsign.NumberGroup)) -self.group:CommandSetCallsign(self.callsign.NumberSquad,self.callsign.NumberGroup) -self.callsignName=UTILS.GetCallsignName(self.callsign.NumberSquad).."-"..self.callsign.NumberGroup -self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) -for _,_element in pairs(self.elements)do -local element=_element -if element.status~=OPSGROUP.ElementStatus.DEAD then -element.callsign=element.unit:GetCallsign() -end -end -else -self:T(self.lid.."ERROR: Group is not alive and not in utero! Cannot switch callsign") -end -return self -end -function OPSGROUP:GetCallsignName(ShortCallsign,Keepnumber,CallsignTranslations) -local element=self:GetElementAlive() -if element then -self:T2(self.lid..string.format("Callsign %s",tostring(element.callsign))) -local name=element.callsign or"Ghostrider11" -name=name:gsub("-","") -if self.group:IsPlayer()or CallsignTranslations then -name=self.group:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) -end -return name -end -return"Ghostrider11" -end -function OPSGROUP:_UpdatePosition() -if self:IsExist()then -self.positionLast=self.position or self:GetVec3() -self.headingLast=self.heading or self:GetHeading() -self.orientXLast=self.orientX or self:GetOrientationX() -self.velocityLast=self.velocity or self.group:GetVelocityMPS() -self.position=self:GetVec3() -self.heading=self:GetHeading() -self.orientX=self:GetOrientationX() -self.velocity=self:GetVelocity() -for _,_element in pairs(self.elements)do -local element=_element -element.vec3=self:GetVec3(element.name) -end -local Tnow=timer.getTime() -self.dTpositionUpdate=self.TpositionUpdate and Tnow-self.TpositionUpdate or 0 -self.TpositionUpdate=Tnow -if not self.traveldist then -self.traveldist=0 -end -self.travelds=UTILS.VecNorm(UTILS.VecSubstract(self.position,self.positionLast)) -self.traveldist=self.traveldist+self.travelds -end -return self -end -function OPSGROUP:_AllSameStatus(status) -for _,_element in pairs(self.elements)do -local element=_element -if element.status==OPSGROUP.ElementStatus.DEAD then -elseif element.status~=status then -return false -end -end -return true -end -function OPSGROUP:_AllSimilarStatus(status) -if status==OPSGROUP.ElementStatus.DEAD then -for _,_element in pairs(self.elements)do -local element=_element -if element.status~=OPSGROUP.ElementStatus.DEAD then -return false -end -end -return true -end -for _,_element in pairs(self.elements)do -local element=_element -self:T2(self.lid..string.format("Status=%s, element %s status=%s",status,element.name,element.status)) -if element.status~=OPSGROUP.ElementStatus.DEAD then -if status==OPSGROUP.ElementStatus.INUTERO then -if element.status~=status then -return false -end -elseif status==OPSGROUP.ElementStatus.SPAWNED then -if element.status~=status and -element.status==OPSGROUP.ElementStatus.INUTERO then -return false -end -elseif status==OPSGROUP.ElementStatus.PARKING then -if element.status~=status or -(element.status==OPSGROUP.ElementStatus.INUTERO or -element.status==OPSGROUP.ElementStatus.SPAWNED)then -return false -end -elseif status==OPSGROUP.ElementStatus.ENGINEON then -if element.status~=status and -(element.status==OPSGROUP.ElementStatus.INUTERO or -element.status==OPSGROUP.ElementStatus.SPAWNED or -element.status==OPSGROUP.ElementStatus.PARKING)then -return false -end -elseif status==OPSGROUP.ElementStatus.TAXIING then -if element.status~=status and -(element.status==OPSGROUP.ElementStatus.INUTERO or -element.status==OPSGROUP.ElementStatus.SPAWNED or -element.status==OPSGROUP.ElementStatus.PARKING or -element.status==OPSGROUP.ElementStatus.ENGINEON)then -return false -end -elseif status==OPSGROUP.ElementStatus.TAKEOFF then -if element.status~=status and -(element.status==OPSGROUP.ElementStatus.INUTERO or -element.status==OPSGROUP.ElementStatus.SPAWNED or -element.status==OPSGROUP.ElementStatus.PARKING or -element.status==OPSGROUP.ElementStatus.ENGINEON or -element.status==OPSGROUP.ElementStatus.TAXIING)then -return false -end -elseif status==OPSGROUP.ElementStatus.AIRBORNE then -if element.status~=status and -(element.status==OPSGROUP.ElementStatus.INUTERO or -element.status==OPSGROUP.ElementStatus.SPAWNED or -element.status==OPSGROUP.ElementStatus.PARKING or -element.status==OPSGROUP.ElementStatus.ENGINEON or -element.status==OPSGROUP.ElementStatus.TAXIING or -element.status==OPSGROUP.ElementStatus.TAKEOFF)then -return false -end -elseif status==OPSGROUP.ElementStatus.LANDED then -if element.status~=status and -(element.status==OPSGROUP.ElementStatus.AIRBORNE or -element.status==OPSGROUP.ElementStatus.LANDING)then -return false -end -elseif status==OPSGROUP.ElementStatus.ARRIVED then -if element.status~=status and -(element.status==OPSGROUP.ElementStatus.AIRBORNE or -element.status==OPSGROUP.ElementStatus.LANDING or -element.status==OPSGROUP.ElementStatus.LANDED)then -return false -end -end -else -end -end -self:T2(self.lid..string.format("All %d elements have similar status %s ==> returning TRUE",#self.elements,status)) -return true -end -function OPSGROUP:_UpdateStatus(element,newstatus,airbase) -local oldstatus=element.status -element.status=newstatus -self:T3(self.lid..string.format("UpdateStatus element=%s: %s --> %s",element.name,oldstatus,newstatus)) -for _,_element in pairs(self.elements)do -local Element=_element -self:T3(self.lid..string.format("Element %s: %s",Element.name,Element.status)) -end -if newstatus==OPSGROUP.ElementStatus.INUTERO then -if self:_AllSimilarStatus(newstatus)then -self:InUtero() -end -elseif newstatus==OPSGROUP.ElementStatus.SPAWNED then -if self:_AllSimilarStatus(newstatus)then -self:Spawned() -end -elseif newstatus==OPSGROUP.ElementStatus.PARKING then -if self:_AllSimilarStatus(newstatus)then -self:Parking() -end -elseif newstatus==OPSGROUP.ElementStatus.ENGINEON then -elseif newstatus==OPSGROUP.ElementStatus.TAXIING then -if self:_AllSimilarStatus(newstatus)then -self:Taxiing() -end -elseif newstatus==OPSGROUP.ElementStatus.TAKEOFF then -if self:_AllSimilarStatus(newstatus)then -self:Takeoff(airbase) -end -elseif newstatus==OPSGROUP.ElementStatus.AIRBORNE then -if self:_AllSimilarStatus(newstatus)then -self:Airborne() -end -elseif newstatus==OPSGROUP.ElementStatus.LANDED then -if self:_AllSimilarStatus(newstatus)then -if self:IsLandingAt()then -self:LandedAt() -else -self:Landed(airbase) -end -end -elseif newstatus==OPSGROUP.ElementStatus.ARRIVED then -if self:_AllSimilarStatus(newstatus)then -if self:IsLanded()then -self:Arrived() -elseif self:IsAirborne()then -self:Landed() -self:Arrived() -end -end -elseif newstatus==OPSGROUP.ElementStatus.DEAD then -if self:_AllSimilarStatus(newstatus)then -self:Dead() -end -end -end -function OPSGROUP:_SetElementStatusAll(status) -for _,_element in pairs(self.elements)do -local element=_element -if element.status~=OPSGROUP.ElementStatus.DEAD then -element.status=status -end -end -end -function OPSGROUP:GetElementByName(unitname) -if unitname and type(unitname)=="string"then -for _,_element in pairs(self.elements)do -local element=_element -if element.name==unitname then -return element -end -end -end -return nil -end -function OPSGROUP:GetElementZoneBoundingBox(UnitName) -local element=self:GetElementByName(UnitName) -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -element.zoneBoundingbox=element.zoneBoundingbox or ZONE_POLYGON_BASE:New(element.name.." Zone Bounding Box",{}) -local l=element.length -local w=element.width -local X=self:GetOrientationX(element.name) -local heading=math.deg(math.atan2(X.z,X.x)) -self:T(self.lid..string.format("Element %s bouding box: l=%d w=%d heading=%d",element.name,l,w,heading)) -local b={} -b[1]={x=l/2,y=-w/2} -b[2]={x=l/2,y=w/2} -b[3]={x=-l/2,y=w/2} -b[4]={x=-l/2,y=-w/2} -for i,p in pairs(b)do -b[i]=UTILS.Vec2Rotate2D(p,heading) -end -local vec2=self:GetVec2(element.name) -local d=UTILS.Vec2Norm(vec2) -local h=UTILS.Vec2Hdg(vec2) -for i,p in pairs(b)do -b[i]=UTILS.Vec2Translate(p,d,h) -end -element.zoneBoundingbox:UpdateFromVec2(b) -return element.zoneBoundingbox -end -return nil -end -function OPSGROUP:GetElementZoneLoad(UnitName) -local element=self:GetElementByName(UnitName) -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -element.zoneLoad=element.zoneLoad or ZONE_POLYGON_BASE:New(element.name.." Zone Load",{}) -self:_GetElementZoneLoader(element,element.zoneLoad,self.carrierLoader) -return element.zoneLoad -end -return nil -end -function OPSGROUP:GetElementZoneUnload(UnitName) -local element=self:GetElementByName(UnitName) -if element and element.status~=OPSGROUP.ElementStatus.DEAD then -element.zoneUnload=element.zoneUnload or ZONE_POLYGON_BASE:New(element.name.." Zone Unload",{}) -self:_GetElementZoneLoader(element,element.zoneUnload,self.carrierUnloader) -return element.zoneUnload -end -return nil -end -function OPSGROUP:_GetElementZoneLoader(Element,Zone,Loader) -if Element.status~=OPSGROUP.ElementStatus.DEAD then -local l=Element.length -local w=Element.width -local X=self:GetOrientationX(Element.name) -local heading=math.deg(math.atan2(X.z,X.x)) -local b={} -if Loader.type:lower()=="front"then -table.insert(b,{x=l/2,y=-Loader.width/2}) -table.insert(b,{x=l/2+Loader.length,y=-Loader.width/2}) -table.insert(b,{x=l/2+Loader.length,y=Loader.width/2}) -table.insert(b,{x=l/2,y=Loader.width/2}) -elseif Loader.type:lower()=="back"then -table.insert(b,{x=-l/2,y=-Loader.width/2}) -table.insert(b,{x=-l/2-Loader.length,y=-Loader.width/2}) -table.insert(b,{x=-l/2-Loader.length,y=Loader.width/2}) -table.insert(b,{x=-l/2,y=Loader.width/2}) -elseif Loader.type:lower()=="left"then -table.insert(b,{x=Loader.length/2,y=-w/2}) -table.insert(b,{x=Loader.length/2,y=-w/2-Loader.width}) -table.insert(b,{x=-Loader.length/2,y=-w/2-Loader.width}) -table.insert(b,{x=-Loader.length/2,y=-w/2}) -elseif Loader.type:lower()=="right"then -table.insert(b,{x=Loader.length/2,y=w/2}) -table.insert(b,{x=Loader.length/2,y=w/2+Loader.width}) -table.insert(b,{x=-Loader.length/2,y=w/2+Loader.width}) -table.insert(b,{x=-Loader.length/2,y=w/2}) -else -b[1]={x=l/2,y=-w/2} -b[2]={x=l/2,y=w/2} -b[3]={x=-l/2,y=w/2} -b[4]={x=-l/2,y=-w/2} -table.insert(b,{x=b[1].x+Loader.length,y=b[1].y-Loader.width}) -table.insert(b,{x=b[2].x+Loader.length,y=b[2].y+Loader.width}) -table.insert(b,{x=b[3].x-Loader.length,y=b[3].y+Loader.width}) -table.insert(b,{x=b[4].x-Loader.length,y=b[4].y-Loader.width}) -end -for i,p in pairs(b)do -b[i]=UTILS.Vec2Rotate2D(p,heading) -end -local vec2=self:GetVec2(Element.name) -local d=UTILS.Vec2Norm(vec2) -local h=UTILS.Vec2Hdg(vec2) -for i,p in pairs(b)do -b[i]=UTILS.Vec2Translate(p,d,h) -end -Zone:UpdateFromVec2(b) -return Zone -end -return nil -end -function OPSGROUP:GetElementAlive() -for _,_element in pairs(self.elements)do -local element=_element -if element.status~=OPSGROUP.ElementStatus.DEAD then -if element.unit and element.unit:IsAlive()then -return element -end -end -end -return nil -end -function OPSGROUP:GetNelements(status) -local n=0 -for _,_element in pairs(self.elements)do -local element=_element -if element.status~=OPSGROUP.ElementStatus.DEAD then -if element.unit and element.unit:IsAlive()then -if status==nil or element.status==status then -n=n+1 -end -end -end -end -return n -end -function OPSGROUP:GetAmmoElement(element) -return self:GetAmmoUnit(element.unit) -end -function OPSGROUP:GetAmmoTot() -local units=self.group:GetUnits() -local Ammo={} -Ammo.Total=0 -Ammo.Guns=0 -Ammo.Rockets=0 -Ammo.Bombs=0 -Ammo.Torpedos=0 -Ammo.Missiles=0 -Ammo.MissilesAA=0 -Ammo.MissilesAG=0 -Ammo.MissilesAS=0 -Ammo.MissilesCR=0 -Ammo.MissilesSA=0 -for _,_unit in pairs(units or{})do -local unit=_unit -if unit and unit:IsExist()then -local ammo=self:GetAmmoUnit(unit) -Ammo.Total=Ammo.Total+ammo.Total -Ammo.Guns=Ammo.Guns+ammo.Guns -Ammo.Rockets=Ammo.Rockets+ammo.Rockets -Ammo.Bombs=Ammo.Bombs+ammo.Bombs -Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos -Ammo.Missiles=Ammo.Missiles+ammo.Missiles -Ammo.MissilesAA=Ammo.MissilesAA+ammo.MissilesAA -Ammo.MissilesAG=Ammo.MissilesAG+ammo.MissilesAG -Ammo.MissilesAS=Ammo.MissilesAS+ammo.MissilesAS -Ammo.MissilesCR=Ammo.MissilesCR+ammo.MissilesCR -Ammo.MissilesSA=Ammo.MissilesSA+ammo.MissilesSA -end -end -return Ammo -end -function OPSGROUP:GetAmmoUnit(unit,display) -if display==nil then -display=false -end -local nammo=0 -local nshells=0 -local nrockets=0 -local nmissiles=0 -local nmissilesAA=0 -local nmissilesAG=0 -local nmissilesAS=0 -local nmissilesSA=0 -local nmissilesBM=0 -local nmissilesCR=0 -local ntorps=0 -local nbombs=0 -unit=unit or self.group:GetUnit(1) -if unit and unit:IsExist()then -local text=string.format("OPSGROUP group %s - unit %s:\n",self.groupname,unit:GetName()) -local ammotable=unit:GetAmmo() -if ammotable then -local weapons=#ammotable -for w=1,weapons do -local Nammo=ammotable[w]["count"] -local rmin=ammotable[w]["desc"]["rangeMin"]or 0 -local rmax=ammotable[w]["desc"]["rangeMaxAltMin"]or 0 -local Tammo=ammotable[w]["desc"]["typeName"] -local _weaponString=UTILS.Split(Tammo,"%.") -local _weaponName=_weaponString[#_weaponString] -local Category=ammotable[w].desc.category -local MissileCategory=nil -if Category==Weapon.Category.MISSILE then -MissileCategory=ammotable[w].desc.missileCategory -end -if Category==Weapon.Category.SHELL then -nshells=nshells+Nammo -text=text..string.format("- %d shells of type %s, range=%d - %d meters\n",Nammo,_weaponName,rmin,rmax) -elseif Category==Weapon.Category.ROCKET then -nrockets=nrockets+Nammo -text=text..string.format("- %d rockets of type %s, \n",Nammo,_weaponName,rmin,rmax) -elseif Category==Weapon.Category.BOMB then -nbombs=nbombs+Nammo -text=text..string.format("- %d bombs of type %s\n",Nammo,_weaponName) -elseif Category==Weapon.Category.MISSILE then -if MissileCategory==Weapon.MissileCategory.AAM then -nmissiles=nmissiles+Nammo -nmissilesAA=nmissilesAA+Nammo -elseif MissileCategory==Weapon.MissileCategory.SAM then -nmissiles=nmissiles+Nammo -nmissilesSA=nmissilesSA+Nammo -elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then -nmissiles=nmissiles+Nammo -nmissilesAS=nmissilesAS+Nammo -elseif MissileCategory==Weapon.MissileCategory.BM then -nmissiles=nmissiles+Nammo -nmissilesBM=nmissilesBM+Nammo -elseif MissileCategory==Weapon.MissileCategory.CRUISE then -nmissiles=nmissiles+Nammo -nmissilesCR=nmissilesCR+Nammo -elseif MissileCategory==Weapon.MissileCategory.OTHER then -nmissiles=nmissiles+Nammo -nmissilesAG=nmissilesAG+Nammo -end -text=text..string.format("- %d %s missiles of type %s, range=%d - %d meters\n",Nammo,self:_MissileCategoryName(MissileCategory),_weaponName,rmin,rmax) -elseif Category==Weapon.Category.TORPEDO then -ntorps=ntorps+Nammo -text=text..string.format("- %d torpedos of type %s\n",Nammo,_weaponName) -else -text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n",Nammo,Tammo,Category,tostring(MissileCategory)) -end -end -end -if display then -self:I(self.lid..text) -else -self:T3(self.lid..text) -end -end -nammo=nshells+nrockets+nmissiles+nbombs+ntorps -local ammo={} -ammo.Total=nammo -ammo.Guns=nshells -ammo.Rockets=nrockets -ammo.Bombs=nbombs -ammo.Torpedos=ntorps -ammo.Missiles=nmissiles -ammo.MissilesAA=nmissilesAA -ammo.MissilesAG=nmissilesAG -ammo.MissilesAS=nmissilesAS -ammo.MissilesCR=nmissilesCR -ammo.MissilesBM=nmissilesBM -ammo.MissilesSA=nmissilesSA -return ammo -end -function OPSGROUP:_MissileCategoryName(categorynumber) -local cat="unknown" -if categorynumber==Weapon.MissileCategory.AAM then -cat="air-to-air" -elseif categorynumber==Weapon.MissileCategory.SAM then -cat="surface-to-air" -elseif categorynumber==Weapon.MissileCategory.BM then -cat="ballistic" -elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then -cat="anti-ship" -elseif categorynumber==Weapon.MissileCategory.CRUISE then -cat="cruise" -elseif categorynumber==Weapon.MissileCategory.OTHER then -cat="other" -end -return cat -end -function OPSGROUP:_PassedFinalWaypoint(final,comment) -self:T(self.lid..string.format("Passed final waypoint=%s [from %s]: comment \"%s\"",tostring(final),tostring(self.passedfinalwp),tostring(comment))) -if final==true and not self.passedfinalwp then -self:PassedFinalWaypoint() -end -self.passedfinalwp=final -end -function OPSGROUP:_CoordinateFromObject(Object) -if Object then -if Object:IsInstanceOf("COORDINATE")then -return Object -else -if Object:IsInstanceOf("POSITIONABLE")or Object:IsInstanceOf("ZONE_BASE")then -self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate") -local coord=Object:GetCoordinate() -return coord -else -self:T(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!") -end -end -else -self:T(self.lid.."ERROR: Object passed is nil!") -end -return nil -end -function OPSGROUP:_IsElement(unitname) -for _,_element in pairs(self.elements)do -local element=_element -if element.name==unitname then -return true -end -end -return false -end -function OPSGROUP:CountElements(States) -if States then -if type(States)=="string"then -States={States} -end -else -States=OPSGROUP.ElementStatus -end -local IncludeDeads=true -local N=0 -for _,_element in pairs(self.elements)do -local element=_element -if element and(IncludeDeads or element.status~=OPSGROUP.ElementStatus.DEAD)then -for _,state in pairs(States)do -if element.status==state then -N=N+1 -break -end -end -end -end -return N -end -function OPSGROUP:_AddElementByName(unitname) -local unit=UNIT:FindByName(unitname) -if unit then -local unittemplate=unit:GetTemplate() -local element=self:GetElementByName(unitname) -if element then -else -element={} -element.status=OPSGROUP.ElementStatus.INUTERO -table.insert(self.elements,element) -end -element.name=unitname -element.unit=unit -element.DCSunit=Unit.getByName(unitname) -element.gid=element.DCSunit:getNumber() -element.uid=element.DCSunit:getID() -element.controller=element.DCSunit:getController() -element.Nhit=0 -element.opsgroup=self -element.skill=unittemplate.skill or"Unknown" -if element.skill=="Client"or element.skill=="Player"then -element.ai=false -element.client=CLIENT:FindByName(unitname) -element.playerName=element.DCSunit:getPlayerName() -else -element.ai=true -end -element.descriptors=unit:GetDesc() -element.category=unit:GetUnitCategory() -element.categoryname=unit:GetCategoryName() -element.typename=unit:GetTypeName() -element.ammo0=self:GetAmmoUnit(unit,false) -element.life=unit:GetLife() -element.life0=math.max(unit:GetLife0(),element.life) -element.size,element.length,element.height,element.width=unit:GetObjectSize() -element.weightEmpty=element.descriptors.massEmpty or 666 -if self.isArmygroup then -element.weightMaxTotal=element.weightEmpty+10*95 -elseif self.isNavygroup then -element.weightMaxTotal=element.weightEmpty+10*1000 -else -element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+8*95 -end -unit:SetCargoBayWeightLimit() -element.weightMaxCargo=unit.__.CargoBayWeightLimit -if element.cargoBay then -element.weightCargo=self:GetWeightCargo(element.name,false) -else -element.cargoBay={} -element.weightCargo=0 -end -element.weight=element.weightEmpty+element.weightCargo -if self.isFlightgroup then -element.callsign=element.unit:GetCallsign() -element.modex=unittemplate.onboard_num -element.payload=unittemplate.payload -element.pylons=unittemplate.payload and unittemplate.payload.pylons or nil -element.fuelmass0=unittemplate.payload and unittemplate.payload.fuel or 0 -element.fuelmass=element.fuelmass0 -element.fuelrel=element.unit:GetFuel() -else -element.callsign="Peter-1-1" -element.modex="000" -element.payload={} -element.pylons={} -element.fuelmass0=99999 -element.fuelmass=99999 -element.fuelrel=1 -end -local text=string.format("Adding element %s: status=%s, skill=%s, life=%.1f/%.1f category=%s (%d), type=%s, size=%.1f (L=%.1f H=%.1f W=%.1f), weight=%.1f/%.1f (cargo=%.1f/%.1f)", -element.name,element.status,element.skill,element.life,element.life0,element.categoryname,element.category,element.typename, -element.size,element.length,element.height,element.width,element.weight,element.weightMaxTotal,element.weightCargo,element.weightMaxCargo) -self:T(self.lid..text) -if unit:IsAlive()and element.status~=OPSGROUP.ElementStatus.SPAWNED then -self:__ElementSpawned(0.05,element) -end -return element -end -return nil -end -function OPSGROUP:_SetTemplate(Template) -self.template=Template or UTILS.DeepCopy(_DATABASE:GetGroupTemplate(self.groupname)) -self:T3(self.lid.."Setting group template") -return self -end -function OPSGROUP:_GetTemplate(Copy) -if self.template then -if Copy then -local template=UTILS.DeepCopy(self.template) -return template -else -return self.template -end -else -self:T(self.lid..string.format("ERROR: No template was set yet!")) -end -return nil -end -function OPSGROUP:ClearWaypoints(IndexMin,IndexMax) -IndexMin=IndexMin or 1 -IndexMax=IndexMax or#self.waypoints -for i=IndexMax,IndexMin,-1 do -table.remove(self.waypoints,i) -end -end -function OPSGROUP:_GetDetectedTarget() -local targetgroup=nil -local targetdist=math.huge -for _,_group in pairs(self.detectedgroups:GetSet())do -local group=_group -if group and group:IsAlive()then -local targetVec3=group:GetVec3() -local distance=UTILS.VecDist3D(self.position,targetVec3) -if distance<=self.engagedetectedRmax and distance=1 then -local text=string.format("Added cargo groups:") -local Weight=0 -for _,_cargo in pairs(self:GetCargos())do -local cargo=_cargo -local weight=cargo.opsgroup:GetWeightTotal() -Weight=Weight+weight -text=text..string.format("\n- %s [%s] weight=%.1f kg",cargo.opsgroup:GetName(),cargo.opsgroup:GetState(),weight) -end -text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg",self.Ncargo,Weight) -self:I(self.lid..text) -end -return self -end -function OPSTRANSPORT:AddCargoStorage(StorageFrom,StorageTo,CargoType,CargoAmount,CargoWeight,TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -local cargo=self:_CreateCargoStorage(StorageFrom,StorageTo,CargoType,CargoAmount,CargoWeight,TransportZoneCombo) -if cargo then -self.Ncargo=self.Ncargo+1 -table.insert(TransportZoneCombo.Cargos,cargo) -end -end -function OPSTRANSPORT:SetPickupZone(PickupZone,TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -TransportZoneCombo.PickupZone=PickupZone -if PickupZone and PickupZone:IsInstanceOf("ZONE_AIRBASE")then -TransportZoneCombo.PickupAirbase=PickupZone._.ZoneAirbase -end -return self -end -function OPSTRANSPORT:GetPickupZone(TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -return TransportZoneCombo.PickupZone -end -function OPSTRANSPORT:SetDeployZone(DeployZone,TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -TransportZoneCombo.DeployZone=DeployZone -if DeployZone and DeployZone:IsInstanceOf("ZONE_AIRBASE")then -TransportZoneCombo.DeployAirbase=DeployZone._.ZoneAirbase -end -return self -end -function OPSTRANSPORT:GetDeployZone(TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -return TransportZoneCombo.DeployZone -end -function OPSTRANSPORT:SetEmbarkZone(EmbarkZone,TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -TransportZoneCombo.EmbarkZone=EmbarkZone or TransportZoneCombo.PickupZone -return self -end -function OPSTRANSPORT:GetEmbarkZone(TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -return TransportZoneCombo.EmbarkZone -end -function OPSTRANSPORT:SetDisembarkZone(DisembarkZone,TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -TransportZoneCombo.DisembarkZone=DisembarkZone -return self -end -function OPSTRANSPORT:GetDisembarkZone(TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -return TransportZoneCombo.DisembarkZone -end -function OPSTRANSPORT:SetDisembarkActivation(Active,TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -if Active==true or Active==nil then -TransportZoneCombo.disembarkActivation=true -else -TransportZoneCombo.disembarkActivation=false -end -return self -end -function OPSTRANSPORT:GetDisembarkActivation(TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -return TransportZoneCombo.disembarkActivation -end -function OPSTRANSPORT:SetDisembarkCarriers(Carriers,TransportZoneCombo) -self:T(self.lid.."Setting transfer carriers!") -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -TransportZoneCombo.disembarkToCarriers=true -self:_AddDisembarkCarriers(Carriers,TransportZoneCombo.DisembarkCarriers) -return self -end -function OPSTRANSPORT:_AddDisembarkCarriers(Carriers,Table) -if Carriers:IsInstanceOf("GROUP")or Carriers:IsInstanceOf("OPSGROUP")then -local carrier=self:_GetOpsGroupFromObject(Carriers) -if carrier then -table.insert(Table,carrier) -end -elseif Carriers:IsInstanceOf("SET_GROUP")or Carriers:IsInstanceOf("SET_OPSGROUP")then -for _,object in pairs(Carriers:GetSet())do -local carrier=self:_GetOpsGroupFromObject(object) -if carrier then -table.insert(Table,carrier) -end -end -else -self:E(self.lid.."ERROR: Carriers must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!") -end -end -function OPSTRANSPORT:GetDisembarkCarriers(TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -return TransportZoneCombo.DisembarkCarriers -end -function OPSTRANSPORT:SetDisembarkInUtero(InUtero,TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -if InUtero==true or InUtero==nil then -TransportZoneCombo.disembarkInUtero=true -else -TransportZoneCombo.disembarkInUtero=false -end -return self -end -function OPSTRANSPORT:GetDisembarkInUtero(TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -return TransportZoneCombo.disembarkInUtero -end -function OPSTRANSPORT:SetFormationPickup(Formation,TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -TransportZoneCombo.PickupFormation=Formation -return self -end -function OPSTRANSPORT:_GetFormationDefault(OpsGroup) -if OpsGroup.isArmygroup then -return self.formationArmy -elseif OpsGroup.isFlightgroup then -if OpsGroup.isHelo then -return self.formationHelo -else -return self.formationPlane -end -else -return ENUMS.Formation.Vehicle.OffRoad -end -return nil -end -function OPSTRANSPORT:_GetFormationPickup(TransportZoneCombo,OpsGroup) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -local formation=TransportZoneCombo.PickupFormation or self:_GetFormationDefault(OpsGroup) -return formation -end -function OPSTRANSPORT:SetFormationTransport(Formation,TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -TransportZoneCombo.TransportFormation=Formation -return self -end -function OPSTRANSPORT:_GetFormationTransport(TransportZoneCombo,OpsGroup) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -local formation=TransportZoneCombo.TransportFormation or self:_GetFormationDefault(OpsGroup) -return formation -end -function OPSTRANSPORT:SetRequiredCargos(Cargos,TransportZoneCombo) -self:T(self.lid.."Setting required cargos!") -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -TransportZoneCombo.RequiredCargos=TransportZoneCombo.RequiredCargos or{} -if Cargos:IsInstanceOf("GROUP")or Cargos:IsInstanceOf("OPSGROUP")then -local cargo=self:_GetOpsGroupFromObject(Cargos) -if cargo then -table.insert(TransportZoneCombo.RequiredCargos,cargo) -end -elseif Cargos:IsInstanceOf("SET_GROUP")or Cargos:IsInstanceOf("SET_OPSGROUP")then -for _,object in pairs(Cargos:GetSet())do -local cargo=self:_GetOpsGroupFromObject(object) -if cargo then -table.insert(TransportZoneCombo.RequiredCargos,cargo) -end -end -else -self:E(self.lid.."ERROR: Required Cargos must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!") -end -return self -end -function OPSTRANSPORT:GetRequiredCargos(TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -return TransportZoneCombo.RequiredCargos -end -function OPSTRANSPORT:SetRequiredCarriers(NcarriersMin,NcarriersMax) -self.nCarriersMin=NcarriersMin or 1 -self.nCarriersMax=NcarriersMax or self.nCarriersMin -if self.nCarriersMax0 then -self:ScheduleOnce(Delay,OPSTRANSPORT._DelCarrier,self,CarrierGroup) -else -if self:IsCarrier(CarrierGroup)then -for i=#self.carriers,1,-1 do -local carrier=self.carriers[i] -if carrier.groupname==CarrierGroup.groupname then -self:T(self.lid..string.format("Removing carrier %s",CarrierGroup.groupname)) -table.remove(self.carriers,i) -end -end -end -end -return self -end -function OPSTRANSPORT:_GetCarrierNames() -local names={} -for _,_carrier in pairs(self.carriers)do -local carrier=_carrier -if carrier:IsAlive()~=nil then -table.insert(names,carrier.groupname) -end -end -return names -end -function OPSTRANSPORT:GetCargoOpsGroups(Delivered,Carrier,TransportZoneCombo) -local cargos=self:GetCargos(TransportZoneCombo,Carrier,Delivered) -local opsgroups={} -for _,_cargo in pairs(cargos)do -local cargo=_cargo -if cargo.type=="OPSGROUP"then -if cargo.opsgroup and not(cargo.opsgroup:IsDead()or cargo.opsgroup:IsStopped())then -table.insert(opsgroups,cargo.opsgroup) -end -end -end -return opsgroups -end -function OPSTRANSPORT:GetCargoStorages(Delivered,Carrier,TransportZoneCombo) -local cargos=self:GetCargos(TransportZoneCombo,Carrier,Delivered) -local opsgroups={} -for _,_cargo in pairs(cargos)do -local cargo=_cargo -if cargo.type=="STORAGE"then -table.insert(opsgroups,cargo.storage) -end -end -return opsgroups -end -function OPSTRANSPORT:GetCarriers() -return self.carriers -end -function OPSTRANSPORT:GetCargos(TransportZoneCombo,Carrier,Delivered) -local tczs=self.tzCombos -if TransportZoneCombo then -tczs={TransportZoneCombo} -end -local cargos={} -for _,_tcz in pairs(tczs)do -local tcz=_tcz -for _,_cargo in pairs(tcz.Cargos)do -local cargo=_cargo -if Delivered==nil or cargo.delivered==Delivered then -if Carrier==nil or Carrier:CanCargo(cargo)then -table.insert(cargos,cargo) -end -end -end -end -return cargos -end -function OPSTRANSPORT:GetCargoTotalWeight(Cargo,IncludeReserved) -local weight=0 -if Cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then -weight=Cargo.opsgroup:GetWeightTotal(nil,IncludeReserved) -else -if type(Cargo.storage.cargoType)=="number"then -if IncludeReserved then -return Cargo.storage.cargoAmount+Cargo.storage.cargoReserved -else -return Cargo.storage.cargoAmount -end -else -if IncludeReserved then -return Cargo.storage.cargoAmount*100 -else -return(Cargo.storage.cargoAmount+Cargo.storage.cargoReserved)*100 -end -end -end -return weight -end -function OPSTRANSPORT:SetTime(ClockStart,ClockStop) -local Tnow=timer.getAbsTime() -local Tstart=Tnow+5 -if ClockStart and type(ClockStart)=="number"then -Tstart=Tnow+ClockStart -elseif ClockStart and type(ClockStart)=="string"then -Tstart=UTILS.ClockToSeconds(ClockStart) -end -local Tstop=nil -if ClockStop and type(ClockStop)=="number"then -Tstop=Tnow+ClockStop -elseif ClockStop and type(ClockStop)=="string"then -Tstop=UTILS.ClockToSeconds(ClockStop) -end -self.Tstart=Tstart -self.Tstop=Tstop -if Tstop then -self.duration=self.Tstop-self.Tstart -end -return self -end -function OPSTRANSPORT:SetPriority(Prio,Importance,Urgent) -self.prio=Prio or 50 -self.urgent=Urgent -self.importance=Importance -return self -end -function OPSTRANSPORT:SetVerbosity(Verbosity) -self.verbose=Verbosity or 0 -return self -end -function OPSTRANSPORT:AddConditionStart(ConditionFunction,...) -if ConditionFunction then -local condition={} -condition.func=ConditionFunction -condition.arg={} -if arg then -condition.arg=arg -end -table.insert(self.conditionStart,condition) -end -return self -end -function OPSTRANSPORT:AddPathTransport(PathGroup,Reversed,Radius,TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -if type(PathGroup)=="string"then -PathGroup=GROUP:FindByName(PathGroup) -end -local path={} -path.category=PathGroup:GetCategory() -path.radius=Radius or 0 -path.waypoints=PathGroup:GetTaskRoute() -table.insert(TransportZoneCombo.TransportPaths,path) -return self -end -function OPSTRANSPORT:_GetPathTransport(Category,TransportZoneCombo) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -local pathsTransport=TransportZoneCombo.TransportPaths -if pathsTransport and#pathsTransport>0 then -local paths={} -for _,_path in pairs(pathsTransport)do -local path=_path -if path.category==Category then -table.insert(paths,path) -end -end -if#paths>0 then -local path=paths[math.random(#paths)] -return path -end -end -return nil -end -function OPSTRANSPORT:SetCarrierTransportStatus(CarrierGroup,Status) -local oldstatus=self:GetCarrierTransportStatus(CarrierGroup) -self:T(self.lid..string.format("New carrier transport status for %s: %s --> %s",CarrierGroup:GetName(),oldstatus,Status)) -self.carrierTransportStatus[CarrierGroup.groupname]=Status -return self -end -function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup) -local status=self.carrierTransportStatus[CarrierGroup.groupname]or"unknown" -return status -end -function OPSTRANSPORT:GetUID() -return self.uid -end -function OPSTRANSPORT:GetNcargoDelivered() -return self.Ndelivered -end -function OPSTRANSPORT:GetNcargoTotal() -return self.Ncargo -end -function OPSTRANSPORT:GetNcarrier() -return self.Ncarrier -end -function OPSTRANSPORT:AddAsset(Asset,TransportZoneCombo) -self:T(self.lid..string.format("Adding asset carrier \"%s\" to transport",tostring(Asset.spawngroupname))) -self.assets=self.assets or{} -table.insert(self.assets,Asset) -return self -end -function OPSTRANSPORT:DelAsset(Asset) -for i,_asset in pairs(self.assets or{})do -local asset=_asset -if asset.uid==Asset.uid then -self:T(self.lid..string.format("Removing asset \"%s\" from transport",tostring(Asset.spawngroupname))) -table.remove(self.assets,i) -return self -end -end -return self -end -function OPSTRANSPORT:AddAssetCargo(Asset,TransportZoneCombo) -self:T(self.lid..string.format("Adding asset cargo \"%s\" to transport and TZC=%s",tostring(Asset.spawngroupname),TransportZoneCombo and TransportZoneCombo.uid or"N/A")) -self.assetsCargo=self.assetsCargo or{} -table.insert(self.assetsCargo,Asset) -TransportZoneCombo.assetsCargo=TransportZoneCombo.assetsCargo or{} -TransportZoneCombo.assetsCargo[Asset.spawngroupname]=Asset -return self -end -function OPSTRANSPORT:GetTZCofCargo(GroupName) -for _,_tzc in pairs(self.tzCombos)do -local tzc=_tzc -for _,_cargo in pairs(tzc.Cargos)do -local cargo=_cargo -if cargo.opsgroup:GetName()==GroupName then -return tzc -end -end -end -return nil -end -function OPSTRANSPORT:AddLegion(Legion) -self:T(self.lid..string.format("Adding legion %s",Legion.alias)) -table.insert(self.legions,Legion) -return self -end -function OPSTRANSPORT:RemoveLegion(Legion) -for i=#self.legions,1,-1 do -local legion=self.legions[i] -if legion.alias==Legion.alias then -self:T(self.lid..string.format("Removing legion %s",Legion.alias)) -table.remove(self.legions,i) -return self -end -end -self:E(self.lid..string.format("ERROR: Legion %s not found and could not be removed!",Legion.alias)) -return self -end -function OPSTRANSPORT:IsCarrier(CarrierGroup) -if CarrierGroup then -for _,_carrier in pairs(self.carriers)do -local carrier=_carrier -if carrier.groupname==CarrierGroup.groupname then -return true -end -end -end -return false -end -function OPSTRANSPORT:IsReadyToGo() -local text=self.lid.."Is ReadyToGo? " -local Tnow=timer.getAbsTime() -local gotzones=false -for _,_tz in pairs(self.tzCombos)do -local tz=_tz -if tz.PickupZone and tz.DeployZone then -gotzones=true -break -end -end -if not gotzones then -text=text.."No, pickup/deploy zone combo not yet defined!" -return false -end -if self.Tstart and Tnowself.Tstop then -text=text.."Nope, stop time already passed!" -self:T(text) -return false -end -local startme=self:EvalConditionsAll(self.conditionStart) -if not startme then -text=text..("No way, at least one start condition is not true!") -self:T(text) -return false -end -text=text.."Yes!" -self:T(text) -return true -end -function OPSTRANSPORT:SetLegionStatus(Legion,Status) -local status=self:GetLegionStatus(Legion) -self:T(self.lid..string.format("Setting LEGION %s to status %s-->%s",Legion.alias,tostring(status),tostring(Status))) -self.statusLegion[Legion.alias]=Status -return self -end -function OPSTRANSPORT:GetLegionStatus(Legion) -local status=self.statusLegion[Legion.alias]or"unknown" -return status -end -function OPSTRANSPORT:IsPlanned() -local is=self:is(OPSTRANSPORT.Status.PLANNED) -return is -end -function OPSTRANSPORT:IsQueued(Legion) -local is=self:is(OPSTRANSPORT.Status.QUEUED) -if Legion then -is=self:GetLegionStatus(Legion)==OPSTRANSPORT.Status.QUEUED -end -return is -end -function OPSTRANSPORT:IsRequested(Legion) -local is=self:is(OPSTRANSPORT.Status.REQUESTED) -if Legion then -is=self:GetLegionStatus(Legion)==OPSTRANSPORT.Status.REQUESTED -end -return is -end -function OPSTRANSPORT:IsScheduled() -local is=self:is(OPSTRANSPORT.Status.SCHEDULED) -return is -end -function OPSTRANSPORT:IsExecuting() -local is=self:is(OPSTRANSPORT.Status.EXECUTING) -return is -end -function OPSTRANSPORT:IsDelivered(Nmin) -local is=self:is(OPSTRANSPORT.Status.DELIVERED) -if is==false and Nmin and self.Ndelivered>=math.min(self.Ncargo,Nmin)then -is=true -end -return is -end -function OPSTRANSPORT:onafterStatusUpdate(From,Event,To) -local fsmstate=self:GetState() -if self.verbose>=1 then -local text=string.format("%s: Ncargo=%d/%d, Ncarrier=%d/%d, Nlegions=%d",fsmstate:upper(),self.Ncargo,self.Ndelivered,#self.carriers,self.Ncarrier,#self.legions) -if self.verbose>=2 then -for i,_tz in pairs(self.tzCombos)do -local tz=_tz -local pickupzone=tz.PickupZone and tz.PickupZone:GetName()or"Unknown" -local deployzone=tz.DeployZone and tz.DeployZone:GetName()or"Unknown" -text=text..string.format("\n[%d] %s --> %s: Ncarriers=%d, Ncargo=%d (%d)",i,pickupzone,deployzone,tz.Ncarriers,#tz.Cargos,tz.Ncargo) -end -end -if self.verbose>=3 then -text=text..string.format("\nCargos:") -for _,_cargo in pairs(self:GetCargos())do -local cargo=_cargo -if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then -local carrier=cargo.opsgroup:_GetMyCarrierElement() -local name=carrier and carrier.name or"none" -local cstate=carrier and carrier.status or"N/A" -text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s], delivered=%s [UID=%s]", -cargo.opsgroup:GetName(),cargo.opsgroup.cargoStatus:upper(),cargo.opsgroup:GetState(),cargo.opsgroup:GetWeightTotal(),name,cstate,tostring(cargo.delivered),tostring(cargo.opsgroup.cargoTransportUID)) -else -local storage=cargo.storage -text=text..string.format("\n- storage type=%s: amount: total=%d loaded=%d, lost=%d, delivered=%d, delivered=%s [UID=%s]", -storage.cargoType,storage.cargoAmount,storage.cargoLoaded,storage.cargoLost,storage.cargoDelivered,tostring(cargo.delivered),tostring(cargo.uid)) -end -end -text=text..string.format("\nCarriers:") -for _,_carrier in pairs(self.carriers)do -local carrier=_carrier -text=text..string.format("\n- %s: %s [%s], Cargo Bay [current/reserved/total]=%d/%d/%d kg [free %d/%d/%d kg]", -carrier:GetName(),carrier.carrierStatus:upper(),carrier:GetState(), -carrier:GetWeightCargo(nil,false),carrier:GetWeightCargo(),carrier:GetWeightCargoMax(), -carrier:GetFreeCargobay(nil,false),carrier:GetFreeCargobay(),carrier:GetFreeCargobayMax()) -end -end -self:I(self.lid..text) -end -self:_CheckDelivered() -if not self:IsDelivered()then -self:__StatusUpdate(-30) -end -end -function OPSTRANSPORT:IsCargoDelivered(GroupName) -for _,_cargo in pairs(self:GetCargos())do -local cargo=_cargo -if cargo.opsgroup:GetName()==GroupName then -return cargo.delivered -end -end -return nil -end -function OPSTRANSPORT:onafterPlanned(From,Event,To) -self:T(self.lid..string.format("New status: %s-->%s",From,To)) -end -function OPSTRANSPORT:onafterScheduled(From,Event,To) -self:T(self.lid..string.format("New status: %s-->%s",From,To)) -end -function OPSTRANSPORT:onafterExecuting(From,Event,To) -self:T(self.lid..string.format("New status: %s-->%s",From,To)) -end -function OPSTRANSPORT:onbeforeDelivered(From,Event,To) -if From==OPSTRANSPORT.Status.DELIVERED then -return false -end -return true -end -function OPSTRANSPORT:onafterDelivered(From,Event,To) -self:T(self.lid..string.format("New status: %s-->%s",From,To)) -for i=#self.carriers,1,-1 do -local carrier=self.carriers[i] -if self:GetCarrierTransportStatus(carrier)~=OPSTRANSPORT.Status.DELIVERED then -carrier:Delivered(self) -end -end -end -function OPSTRANSPORT:onafterLoaded(From,Event,To,OpsGroupCargo,OpsGroupCarrier,CarrierElement) -self:I(self.lid..string.format("Loaded OPSGROUP %s into carrier %s",OpsGroupCargo:GetName(),tostring(CarrierElement.name))) -end -function OPSTRANSPORT:onafterUnloaded(From,Event,To,OpsGroupCargo,OpsGroupCarrier) -self:I(self.lid..string.format("Unloaded OPSGROUP %s",OpsGroupCargo:GetName())) -end -function OPSTRANSPORT:onafterDeadCarrierGroup(From,Event,To,OpsGroup) -self:I(self.lid..string.format("Carrier OPSGROUP %s dead!",OpsGroup:GetName())) -self.NcarrierDead=self.NcarrierDead+1 -self:_DelCarrier(OpsGroup) -if#self.carriers==0 then -self:DeadCarrierAll() -end -end -function OPSTRANSPORT:onafterDeadCarrierAll(From,Event,To) -self:I(self.lid..string.format("ALL Carrier OPSGROUPs are dead!")) -if self.opszone then -self:I(self.lid..string.format("Cancelling transport on CHIEF level")) -self.chief:TransportCancel(self) -else -self:_CheckDelivered() -if not self:IsDelivered()then -self:Planned() -end -end -end -function OPSTRANSPORT:onafterCancel(From,Event,To) -local Ngroups=#self.carriers -self:I(self.lid..string.format("CANCELLING transport in status %s. Will wait for %d carrier groups to report DONE before evaluation",self:GetState(),Ngroups)) -self.Tover=timer.getAbsTime() -if self.chief then -self:T(self.lid..string.format("CHIEF will cancel the transport. Will wait for mission DONE before evaluation!")) -self.chief:TransportCancel(self) -elseif self.commander then -self:T(self.lid..string.format("COMMANDER will cancel the transport. Will wait for transport DELIVERED before evaluation!")) -self.commander:TransportCancel(self) -elseif self.legions and#self.legions>0 then -for _,_legion in pairs(self.legions or{})do -local legion=_legion -self:T(self.lid..string.format("LEGION %s will cancel the transport. Will wait for transport DELIVERED before evaluation!",legion.alias)) -legion:TransportCancel(self) -end -else -self:T(self.lid..string.format("No legion, commander or chief. Attached OPS groups will cancel the transport on their own. Will wait for transport DELIVERED before evaluation!")) -for _,_carrier in pairs(self:GetCarriers())do -local carrier=_carrier -carrier:TransportCancel(self) -end -local cargos=self:GetCargoOpsGroups(false) -for _,_cargo in pairs(cargos)do -local cargo=_cargo -cargo:_DelMyLift(self) -end -end -if self:IsPlanned()or self:IsQueued()or self:IsRequested()or Ngroups==0 then -self:T(self.lid..string.format("Cancelled transport was in %s stage with %d carrier groups assigned and alive. Call it DELIVERED!",self:GetState(),Ngroups)) -self:Delivered() -end -end -function OPSTRANSPORT:_CheckDelivered() -if self.Ncargo>0 then -local done=true -local dead=true -for _,_cargo in pairs(self:GetCargos())do -local cargo=_cargo -if cargo.delivered then -dead=false -elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup==nil then -dead=false -elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsDestroyed()then -elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsDead()then -elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsStopped()then -dead=false -else -done=false -dead=false -end -end -if dead then -self:I(self.lid.."All cargo DEAD ==> Delivered!") -self:Delivered() -elseif done then -self:I(self.lid.."All cargo DONE ==> Delivered!") -self:Delivered() -end -end -end -function OPSTRANSPORT:_CheckRequiredCargos(TransportZoneCombo,CarrierGroup) -TransportZoneCombo=TransportZoneCombo or self.tzcDefault -local requiredCargos=TransportZoneCombo.Cargos -if TransportZoneCombo.RequiredCargos and#TransportZoneCombo.RequiredCargos>0 then -requiredCargos=TransportZoneCombo.RequiredCargos -else -requiredCargos={} -for _,_cargo in pairs(TransportZoneCombo.Cargos)do -local cargo=_cargo -table.insert(requiredCargos,cargo.opsgroup) -end -end -if requiredCargos==nil or#requiredCargos==0 then -return true -end -local carrierNames=self:_GetCarrierNames() -local weightmin=nil -for _,_cargo in pairs(requiredCargos)do -local cargo=_cargo -local isLoaded=cargo:IsLoaded(carrierNames) -if not isLoaded then -local weight=cargo:GetWeightTotal() -if weightmin==nil or weight=1 then -local dist=tz.PickupZone:Get2DDistance(vec2) -local ncarriers=0 -for _,_carrier in pairs(self.carriers)do -local carrier=_carrier -if carrier and carrier:IsAlive()and carrier.cargoTZC and carrier.cargoTZC.uid==tz.uid then -ncarriers=ncarriers+1 -end -end -local candidate={tzc=tz,distance=dist/1000,ncargo=ncargo,ncarriers=ncarriers} -candidate.penalty=penalty(candidate) -table.insert(candidates,candidate) -end -end -end -if#candidates>0 then -local function optTZC(candA,candB) -return candA.penalty=3 then -local text="TZC optimized" -for i,candidate in pairs(candidates)do -text=text..string.format("\n[%d] TPZ=%d, Ncarriers=%d, Ncargo=%d, Distance=%.1f km, PENALTY=%d",i,candidate.tzc.uid,candidate.ncarriers,candidate.ncargo,candidate.distance,candidate.penalty) -end -self:I(self.lid..text) -end -return candidates[1].tzc -else -self:T(self.lid..string.format("Could NOT find a pickup zone (with cargo) for carrier group %s",Carrier:GetName())) -end -return nil -end -function OPSTRANSPORT:_GetOpsGroupFromObject(Object) -local opsgroup=nil -if Object:IsInstanceOf("OPSGROUP")then -opsgroup=Object -elseif Object:IsInstanceOf("GROUP")then -opsgroup=_DATABASE:GetOpsGroup(Object) -if not opsgroup then -if Object:IsAir()then -opsgroup=FLIGHTGROUP:New(Object) -elseif Object:IsShip()then -opsgroup=NAVYGROUP:New(Object) -else -opsgroup=ARMYGROUP:New(Object) -end -end -else -self:E(self.lid.."ERROR: Object must be a GROUP or OPSGROUP object!") -return nil -end -return opsgroup -end -OPSZONE={ -ClassName="OPSZONE", -verbose=0, -Nred=0, -Nblu=0, -Nnut=0, -Ncoal={}, -Tred=0, -Tblu=0, -Tnut=0, -chiefs={}, -Missions={}, -} -OPSZONE.ZoneType={ -Circular="Circular", -Polygon="Polygon", -} -OPSZONE.version="0.6.1" -function OPSZONE:New(Zone,CoalitionOwner) -local self=BASE:Inherit(self,FSM:New()) -if Zone then -if type(Zone)=="string"then -local Name=Zone -Zone=ZONE:FindByName(Name) -if not Zone then -local airbase=AIRBASE:FindByName(Name) -if airbase then -Zone=ZONE_AIRBASE:New(Name,2000) -end -end -if not Zone then -self:E(string.format("ERROR: No ZONE or ZONE_AIRBASE found for name: %s",Name)) -return nil -end -end -else -self:E("ERROR: First parameter Zone is nil in OPSZONE:New(Zone) call!") -return nil -end -if Zone:IsInstanceOf("ZONE_AIRBASE")then -self.airbase=Zone._.ZoneAirbase -self.airbaseName=self.airbase:GetName() -self.zoneType=OPSZONE.ZoneType.Circular -self.zoneCircular=Zone -elseif Zone:IsInstanceOf("ZONE_RADIUS")then -self.zoneType=OPSZONE.ZoneType.Circular -self.zoneCircular=Zone -elseif Zone:IsInstanceOf("ZONE_POLYGON_BASE")then -self.zoneType=OPSZONE.ZoneType.Polygon -local zone=Zone -self.zoneCircular=zone:GetZoneRadius(nil,true) -else -self:E("ERROR: OPSZONE must be a SPHERICAL zone due to DCS restrictions!") -return nil -end -self.lid=string.format("OPSZONE %s | ",Zone:GetName()) -self.zone=Zone -self.zoneName=Zone:GetName() -self.zoneRadius=self.zoneCircular:GetRadius() -self.Missions={} -self.ScanUnitSet=SET_UNIT:New():FilterZones({Zone}) -self.ScanGroupSet=SET_GROUP:New():FilterZones({Zone}) -_DATABASE:AddOpsZone(self) -self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL -self.ownerPrevious=CoalitionOwner or coalition.side.NEUTRAL -self.isContested=false -self.Ncoal[coalition.side.BLUE]=0 -self.Ncoal[coalition.side.RED]=0 -self.Ncoal[coalition.side.NEUTRAL]=0 -if self.airbase then -self.ownerCurrent=self.airbase:GetCoalition() -self.ownerPrevious=self.airbase:GetCoalition() -end -self:SetObjectCategories() -self:SetUnitCategories() -self:SetDrawZone() -self:SetMarkZone(true) -self:SetCaptureTime() -self:SetCaptureNunits() -self:SetCaptureThreatlevel() -self.timerStatus=TIMER:New(OPSZONE.Status,self) -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Empty") -self:AddTransition("*","Stop","Stopped") -self:AddTransition("*","Evaluated","*") -self:AddTransition("*","Captured","Guarded") -self:AddTransition("Empty","Guarded","Guarded") -self:AddTransition("*","Empty","Empty") -self:AddTransition("*","Attacked","Attacked") -self:AddTransition("*","Defeated","Guarded") -return self -end -function OPSZONE:SetVerbosity(VerbosityLevel) -self.verbose=VerbosityLevel or 0 -return self -end -function OPSZONE:SetObjectCategories(Categories) -if Categories and type(Categories)~="table"then -Categories={Categories} -end -self.ObjectCategories=Categories or{Object.Category.UNIT,Object.Category.STATIC} -return self -end -function OPSZONE:SetUnitCategories(Categories) -if Categories and type(Categories)~="table"then -Categories={Categories} -end -self.UnitCategories=Categories or{Unit.Category.GROUND_UNIT} -return self -end -function OPSZONE:SetCaptureThreatlevel(Threatlevel) -self.threatlevelCapture=Threatlevel or 0 -return self -end -function OPSZONE:SetCaptureNunits(Nunits) -Nunits=Nunits or 1 -self.nunitsCapture=Nunits -return self -end -function OPSZONE:SetCaptureTime(Tcapture) -self.TminCaptured=Tcapture or 0 -return self -end -function OPSZONE:SetNeutralCanCapture(CanCapture) -self.neutralCanCapture=CanCapture -return self -end -function OPSZONE:SetDrawZone(Switch) -if Switch==false then -self.drawZone=false -else -self.drawZone=true -end -return self -end -function OPSZONE:SetMarkZone(Switch,ReadOnly) -if Switch then -self.markZone=true -local Coordinate=self:GetCoordinate() -self.markerText=self:_GetMarkerText() -self.marker=self.marker or MARKER:New(Coordinate,self.markerText) -if ReadOnly==false then -self.marker.readonly=false -else -self.marker.readonly=true -end -self.marker:ToAll() -else -if self.marker then -self.marker:Remove() -end -self.marker=nil -self.markZone=false -end -return self -end -function OPSZONE:GetOwner() -return self.ownerCurrent -end -function OPSZONE:GetOwnerName() -return UTILS.GetCoalitionName(self.ownerCurrent) -end -function OPSZONE:GetCoordinate() -local coordinate=self.zone:GetCoordinate() -return coordinate -end -function OPSZONE:GetScannedUnitSet() -return self.ScanUnitSet -end -function OPSZONE:GetScannedGroupSet() -return self.ScanGroupSet -end -function OPSZONE:GetRandomCoordinate(inner,outer,surfacetypes) -local zone=self:GetZone() -local coord=zone:GetRandomCoordinate(inner,outer,surfacetypes) -return coord -end -function OPSZONE:GetName() -return self.zoneName -end -function OPSZONE:GetZone() -return self.zone -end -function OPSZONE:GetPreviousOwner() -return self.ownerPrevious -end -function OPSZONE:GetAttackDuration() -if self:IsAttacked()and self.Tattacked then -local dT=timer.getAbsTime()-self.Tattacked -return dT -end -return nil -end -function OPSZONE:IsRed() -local is=self.ownerCurrent==coalition.side.RED -return is -end -function OPSZONE:IsBlue() -local is=self.ownerCurrent==coalition.side.BLUE -return is -end -function OPSZONE:IsNeutral() -local is=self.ownerCurrent==coalition.side.NEUTRAL -return is -end -function OPSZONE:IsCoalition(Coalition) -local is=self.ownerCurrent==Coalition -return is -end -function OPSZONE:IsStarted() -local is=not self:IsStopped() -return is -end -function OPSZONE:IsStopped() -local is=self:is("Stopped") -return is -end -function OPSZONE:IsGuarded() -local is=self:is("Guarded") -return is -end -function OPSZONE:IsEmpty() -local is=self:is("Empty") -return is -end -function OPSZONE:IsAttacked() -local is=self:is("Attacked") -return is -end -function OPSZONE:IsContested() -return self.isContested -end -function OPSZONE:IsStopped() -local is=self:is("Stopped") -return is -end -function OPSZONE:onafterStart(From,Event,To) -self:I(self.lid..string.format("Starting OPSZONE v%s",OPSZONE.version)) -self.timerStatus=self.timerStatus or TIMER:New(OPSZONE.Status,self) -self.timerStatus:Start(1,120) -if self.airbase then -self:HandleEvent(EVENTS.BaseCaptured) -end -end -function OPSZONE:onafterStop(From,Event,To) -self:I(self.lid..string.format("Stopping OPSZONE")) -self.timerStatus:Stop() -self.zone:UndrawZone() -if self.markZone then -self.marker:Remove() -end -self:UnHandleEvent(EVENTS.BaseCaptured) -self.CallScheduler:Clear() -if self.Scheduler then -self.Scheduler:Clear() -end -end -function OPSZONE:Status() -local fsmstate=self:GetState() -local contested=tostring(self:IsContested()) -if self.verbose>=1 then -local text=string.format("State %s: Owner %d (previous %d), contested=%s, Nunits: red=%d, blue=%d, neutral=%d",fsmstate,self.ownerCurrent,self.ownerPrevious,contested,self.Nred,self.Nblu,self.Nnut) -self:I(self.lid..text) -end -self:Scan() -self:EvaluateZone() -self:_UpdateMarker() -if self.zone.DrawID and not self.drawZone then -self.zone:UndrawZone() -end -end -function OPSZONE:onbeforeCaptured(From,Event,To,NewOwnerCoalition) -if self.ownerCurrent==NewOwnerCoalition then -self:T(self.lid.."") -end -return true -end -function OPSZONE:onafterCaptured(From,Event,To,NewOwnerCoalition) -self:T(self.lid..string.format("Zone captured by coalition=%d",NewOwnerCoalition)) -self.ownerPrevious=self.ownerCurrent -self.ownerCurrent=NewOwnerCoalition -if self.drawZone then -self.zone:UndrawZone() -local color=self:_GetZoneColor() -self.zone:DrawZone(nil,color,1.0,color,0.5) -end -for _,_chief in pairs(self.chiefs)do -local chief=_chief -if chief.coalition==self.ownerCurrent then -chief:ZoneCaptured(self) -else -chief:ZoneLost(self) -end -end -end -function OPSZONE:onafterEmpty(From,Event,To) -self:T(self.lid..string.format("Zone is empty EVENT")) -end -function OPSZONE:onafterAttacked(From,Event,To,AttackerCoalition) -self:T(self.lid..string.format("Zone is being attacked by coalition=%s!",tostring(AttackerCoalition))) -end -function OPSZONE:onafterDefeated(From,Event,To,DefeatedCoalition) -self:T(self.lid..string.format("Defeated attack on zone by coalition=%d",DefeatedCoalition)) -self.Tattacked=nil -end -function OPSZONE:onenterGuarded(From,Event,To) -if From~=To then -self:T(self.lid..string.format("Zone is guarded")) -self.Tattacked=nil -if self.drawZone then -self.zone:UndrawZone() -local color=self:_GetZoneColor() -self.zone:DrawZone(nil,color,1.0,color,0.5) -end -end -end -function OPSZONE:onenterAttacked(From,Event,To,AttackerCoalition) -if From~="Attacked"then -self:T(self.lid..string.format("Zone is Attacked")) -self.Tattacked=timer.getAbsTime() -if AttackerCoalition then -for _,_chief in pairs(self.chiefs)do -local chief=_chief -if chief.coalition~=AttackerCoalition then -chief:ZoneAttacked(self) -end -end -end -if self.drawZone then -self.zone:UndrawZone() -local color={1,204/255,204/255} -self.zone:DrawZone(nil,color,1.0,color,0.5) -end -self:_CleanMissionTable() -end -end -function OPSZONE:onenterEmpty(From,Event,To) -if From~=To then -self:T(self.lid..string.format("Zone is empty now")) -for _,_chief in pairs(self.chiefs)do -local chief=_chief -chief:ZoneEmpty(self) -end -if self.drawZone then -self.zone:UndrawZone() -local color=self:_GetZoneColor() -self.zone:DrawZone(nil,color,1.0,color,0.2) -end -end -end -function OPSZONE:Scan() -if self.verbose>=3 then -local text=string.format("Scanning zone %s R=%.1f m",self.zoneName,self.zoneRadius) -self:I(self.lid..text) -end -local SphereSearch={id=world.VolumeType.SPHERE,params={point=self.zone:GetVec3(),radius=self.zoneRadius}} -local Nred=0 -local Nblu=0 -local Nnut=0 -local Tred=0 -local Tblu=0 -local Tnut=0 -self.ScanGroupSet:Clear(false) -self.ScanUnitSet:Clear(false) -local function EvaluateZone(_ZoneObject) -local ZoneObject=_ZoneObject -if ZoneObject then -local ObjectCategory=Object.getCategory(ZoneObject) -if ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()and ZoneObject:isActive()then -local DCSUnit=ZoneObject -local function Included() -if not self.UnitCategories then -return true -else -local CategoryDCSUnit=ZoneObject:getDesc().category -for _,UnitCategory in pairs(self.UnitCategories)do -if UnitCategory==CategoryDCSUnit then -return true -end -end -end -return false -end -if Included()then -local Coalition=DCSUnit:getCoalition() -local tl=0 -local unit=UNIT:Find(DCSUnit) -if unit then -local inzone=true -if self.zoneType==OPSZONE.ZoneType.Polygon then -inzone=unit:IsInZone(self.zone) -end -if inzone then -tl=unit:GetThreatLevel() -self.ScanUnitSet:AddUnit(unit) -local group=unit:GetGroup() -if group then -self.ScanGroupSet:AddGroup(group,true) -end -if Coalition==coalition.side.RED then -Nred=Nred+1 -Tred=Tred+tl -elseif Coalition==coalition.side.BLUE then -Nblu=Nblu+1 -Tblu=Tblu+tl -elseif Coalition==coalition.side.NEUTRAL then -Nnut=Nnut+1 -Tnut=Tnut+tl -end -if self.verbose>=4 then -self:I(self.lid..string.format("Found unit %s (coalition=%d)",DCSUnit:getName(),Coalition)) -end -end -end -end -elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist()then -local DCSStatic=ZoneObject -local Coalition=DCSStatic:getCoalition() -local inzone=true -if self.zoneType==OPSZONE.ZoneType.Polygon then -local Vec3=DCSStatic:getPoint() -inzone=self.zone:IsVec3InZone(Vec3) -end -if inzone then -if Coalition==coalition.side.RED then -Nred=Nred+1 -elseif Coalition==coalition.side.BLUE then -Nblu=Nblu+1 -elseif Coalition==coalition.side.NEUTRAL then -Nnut=Nnut+1 -end -if self.verbose>=4 then -self:I(self.lid..string.format("Found static %s (coalition=%d)",DCSStatic:getName(),Coalition)) -end -end -elseif ObjectCategory==Object.Category.SCENERY then -local SceneryType=ZoneObject:getTypeName() -local SceneryName=ZoneObject:getName() -self:T2(self.lid..string.format("Found scenery type=%s, name=%s",SceneryType,SceneryName)) -end -end -return true -end -world.searchObjects(self.ObjectCategories,SphereSearch,EvaluateZone) -if self.verbose>=3 then -local text=string.format("Scan result Nred=%d, Nblue=%d, Nneutral=%d",Nred,Nblu,Nnut) -if self.verbose>=4 then -for _,_unit in pairs(self.ScanUnitSet:GetSet())do -local unit=_unit -text=text..string.format("\nUnit %s coalition=%s",unit:GetName(),unit:GetCoalitionName()) -end -for _,_group in pairs(self.ScanGroupSet:GetSet())do -local group=_group -text=text..string.format("\nGroup %s coalition=%s",group:GetName(),group:GetCoalitionName()) -end -end -self:I(self.lid..text) -end -self.Nred=Nred -self.Nblu=Nblu -self.Nnut=Nnut -self.Ncoal[coalition.side.BLUE]=Nblu -self.Ncoal[coalition.side.RED]=Nred -self.Ncoal[coalition.side.NEUTRAL]=Nnut -self.Tblu=Tblu -self.Tred=Tred -self.Tnut=Tnut -return self -end -function OPSZONE:EvaluateZone() -local Nred=self.Nred -local Nblu=self.Nblu -local Nnut=self.Nnut -local Tnow=timer.getAbsTime() -local function captured(coal) -if not self.airbase then -if not self.Tcaptured then -self.Tcaptured=Tnow -end -if Tnow-self.Tcaptured>=self.TminCaptured then -self:Captured(coal) -self.Tcaptured=nil -end -end -end -if self:IsRed()then -if Nred==0 then -if Nblu>=self.nunitsCapture and self.Tblu>=self.threatlevelCapture then -captured(coalition.side.BLUE) -elseif Nnut>=self.nunitsCapture and self.Tnut>=self.threatlevelCapture and self.neutralCanCapture then -captured(coalition.side.NEUTRAL) -end -else -if Nblu>0 then -if not self:IsAttacked()and self.Tnut>=self.threatlevelCapture then -self:Attacked(coalition.side.BLUE) -end -elseif Nblu==0 then -if self:IsAttacked()and self:IsContested()then -self:Defeated(coalition.side.BLUE) -elseif self:IsEmpty()then -self:Guarded() -end -end -end -if Nblu==0 then -self.isContested=false -else -self.isContested=true -end -elseif self:IsBlue()then -if Nblu==0 then -if Nred>=self.nunitsCapture and self.Tred>=self.threatlevelCapture then -captured(coalition.side.RED) -elseif Nnut>=self.nunitsCapture and self.Tnut>=self.threatlevelCapture and self.neutralCanCapture then -captured(coalition.side.NEUTRAL) -end -else -if Nred>0 then -if not self:IsAttacked()and self.Tnut>=self.threatlevelCapture then -self:Attacked(coalition.side.RED) -end -elseif Nred==0 then -if self:IsAttacked()and self:IsContested()then -self:Defeated(coalition.side.RED) -elseif self:IsEmpty()then -self:Guarded() -end -end -end -if Nred==0 then -self.isContested=false -else -self.isContested=true -end -elseif self:IsNeutral()then -if Nred>0 and Nblu>0 then -self:T(self.lid.."FF neutrals left neutral zone and red and blue are present! What to do?") -if not self:IsAttacked()then -self:Attacked() -end -self.isContested=true -elseif Nred>=self.nunitsCapture and self.Tred>=self.threatlevelCapture then -captured(coalition.side.RED) -elseif Nblu>=self.nunitsCapture and self.Tblu>=self.threatlevelCapture then -captured(coalition.side.BLUE) -end -else -self:E(self.lid.."ERROR: Unknown coaliton!") -end -if Nblu==0 and Nred==0 and Nnut==0 and(not self:IsEmpty())then -self:Empty() -end -if self.airbase then -local airbasecoalition=self.airbase:GetCoalition() -if airbasecoalition~=self.ownerCurrent then -self:T(self.lid..string.format("Captured airbase %s: Coaltion %d-->%d",self.airbaseName,self.ownerCurrent,airbasecoalition)) -self:Captured(airbasecoalition) -end -end -self:Evaluated() -end -function OPSZONE:OnEventHit(EventData) -if self.HitsOn then -local UnitHit=EventData.TgtUnit -if UnitHit and UnitHit:IsInZone(self)and UnitHit:GetCoalition()==self.ownerCurrent then -self.HitTimeLast=timer.getTime() -if not self:IsAttacked()then -self:T3(self.lid.."Hit ==> Attack") -self:Attacked() -end -end -end -end -function OPSZONE:OnEventBaseCaptured(EventData) -if EventData and EventData.Place and self.airbase and self.airbaseName then -local airbase=EventData.Place -if EventData.PlaceName==self.airbaseName then -local CoalitionNew=airbase:GetCoalition() -self:I(self.lid..string.format("EVENT BASE CAPTURED: New coalition of airbase %s: %d [previous=%d]",self.airbaseName,CoalitionNew,self.ownerCurrent)) -if CoalitionNew~=self.ownerCurrent then -self:Captured(CoalitionNew) -end -end -end -end -function OPSZONE:_GetZoneColor() -local color={0,0,0} -if self.ownerCurrent==coalition.side.NEUTRAL then -color=self.ZoneOwnerNeutral or{1,1,1} -elseif self.ownerCurrent==coalition.side.BLUE then -color=self.ZoneOwnerBlue or{0,0,1} -elseif self.ownerCurrent==coalition.side.RED then -color=self.ZoneOwnerRed or{1,0,0} -else -end -return color -end -function OPSZONE:SetZoneColor(Neutral,Blue,Red) -self.ZoneOwnerNeutral=Neutral or{1,1,1} -self.ZoneOwnerBlue=Blue or{0,0,1} -self.ZoneOwnerRed=Red or{1,0,0} -return self -end -function OPSZONE:_UpdateMarker() -if self.markZone then -local text=self:_GetMarkerText() -if text~=self.markerText then -self.markerText=text -self.marker:UpdateText(self.markerText) -end -end -end -function OPSZONE:_GetMarkerText() -local owner=UTILS.GetCoalitionName(self.ownerCurrent) -local prevowner=UTILS.GetCoalitionName(self.ownerPrevious) -local text=string.format("%s [N=%d, TL=%d T=%d]:\nOwner=%s [%s]\nState=%s [Contested=%s]\nBlue=%d [TL=%d]\nRed=%d [TL=%d]\nNeutral=%d [TL=%d]", -self.zoneName,self.nunitsCapture or 0,self.threatlevelCapture or 0,self.TminCaptured or 0, -owner,prevowner,self:GetState(),tostring(self:IsContested()), -self.Nblu,self.Tblu,self.Nred,self.Tred,self.Nnut,self.Tnut) -return text -end -function OPSZONE:_AddChief(Chief) -table.insert(self.chiefs,Chief) -end -function OPSZONE:_AddMission(Coalition,Type,Auftrag) -local entry={} -entry.Coalition=Coalition or coalition.side.NEUTRAL -entry.Type=Type or"" -entry.Mission=Auftrag or nil -table.insert(self.Missions,entry) -return self -end -function OPSZONE:_GetMissions() -return self.Missions -end -function OPSZONE:_FindMissions(Coalition,Type) -local foundmissions={} -local found=false -for _,_entry in pairs(self.Missions)do -local entry=_entry -if entry.Coalition==Coalition and entry.Type==Type and entry.Mission and entry.Mission:IsNotOver()then -table.insert(foundmissions,entry.Mission) -found=true -end -end -return found,foundmissions -end -function OPSZONE:_CleanMissionTable() -local missions={} -for _,_entry in pairs(self.Missions)do -local entry=_entry -if entry.Mission and entry.Mission:IsNotOver()then -table.insert(missions,entry) -end -end -self.Missions=missions -return self -end -PLATOON={ -ClassName="PLATOON", -verbose=0, -weaponData={}, -} -PLATOON.version="0.1.0" -function PLATOON:New(TemplateGroupName,Ngroups,PlatoonName) -local self=BASE:Inherit(self,COHORT:New(TemplateGroupName,Ngroups,PlatoonName)) -self:AddMissionCapability(AUFTRAG.Type.NOTHING,50) -self.isGround=true -self.ammo=self:_CheckAmmo() -return self -end -function PLATOON:SetBrigade(Brigade) -self.legion=Brigade -return self -end -function PLATOON:GetBrigade() -return self.legion -end -function PLATOON:onafterStatus(From,Event,To) -if self.verbose>=1 then -local fsmstate=self:GetState() -local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName)or"N/A" -local skill=self.skill and tostring(self.skill)or"N/A" -local NassetsTot=#self.assets -local NassetsInS=self:CountAssets(true) -local NassetsQP=0;local NassetsP=0;local NassetsQ=0 -if self.legion then -NassetsQP,NassetsP,NassetsQ=self.legion:CountAssetsOnMission(nil,self) -end -local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]", -fsmstate,self.aircrafttype,callsign,skill,NassetsTot,NassetsInS,NassetsQP,NassetsP,NassetsQ) -self:T(self.lid..text) -if self.verbose>=3 and self.weaponData then -local text="Weapon Data:" -for bit,_weapondata in pairs(self.weaponData)do -local weapondata=_weapondata -text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km",bit,weapondata.RangeMin/1000,weapondata.RangeMax/1000) -end -self:I(self.lid..text) -end -self:_CheckAssetStatus() -end -if not self:IsStopped()then -self:__Status(-60) -end -end -do -_PlayerTaskNr=0 -PLAYERTASK={ -ClassName="PLAYERTASK", -verbose=false, -lid=nil, -PlayerTaskNr=nil, -Type=nil, -TTSType=nil, -Target=nil, -Clients=nil, -Repeat=false, -repeats=0, -RepeatNo=1, -TargetMarker=nil, -SmokeColor=nil, -FlareColor=nil, -conditionSuccess={}, -conditionFailure={}, -TaskController=nil, -timestamp=0, -lastsmoketime=0, -Freetext=nil, -FreetextTTS=nil, -TaskSubType=nil, -NextTaskSuccess={}, -NextTaskFailure={}, -FinalState="none", -PreviousCount=0, -} -PLAYERTASK.version="0.1.24" -function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType) -local self=BASE:Inherit(self,FSM:New()) -self.Type=Type -self.Repeat=false -self.repeats=0 -self.RepeatNo=1 -self.Clients=FIFO:New() -self.TargetMarker=nil -self.SmokeColor=SMOKECOLOR.Red -self.conditionSuccess={} -self.conditionFailure={} -self.TaskController=nil -self.timestamp=timer.getAbsTime() -self.TTSType=TTSType or"close air support" -self.lastsmoketime=0 -if Repeat then -self.Repeat=true -self.RepeatNo=Times or 1 -end -_PlayerTaskNr=_PlayerTaskNr+1 -self.PlayerTaskNr=_PlayerTaskNr -self.lid=string.format("PlayerTask #%d %s | ",self.PlayerTaskNr,tostring(self.Type)) -if Target and Target.ClassName and Target.ClassName=="TARGET"then -self.Target=Target -elseif Target and Target.ClassName then -self.Target=TARGET:New(Target) -else -self:E(self.lid.."*** NO VALID TARGET!") -return self -end -self.PreviousCount=self.Target:CountTargets() -self:T(self.lid.."Created.") -self:SetStartState("Planned") -self:AddTransition("*","Planned","Planned") -self:AddTransition("*","Requested","Requested") -self:AddTransition("*","ClientAdded","*") -self:AddTransition("*","ClientRemoved","*") -self:AddTransition("*","Executing","Executing") -self:AddTransition("*","Progress","*") -self:AddTransition("*","Done","Done") -self:AddTransition("*","Cancel","Done") -self:AddTransition("*","Success","Done") -self:AddTransition("*","ClientAborted","*") -self:AddTransition("*","Failed","Failed") -self:AddTransition("*","Status","*") -self:AddTransition("*","Stop","Stopped") -self:__Status(-5) -return self -end -function PLAYERTASK:_SetController(Controller) -self:T(self.lid.."_SetController") -self.TaskController=Controller -return self -end -function PLAYERTASK:SetCoalition(Coalition) -self:T(self.lid.."SetCoalition") -self.coalition=Coalition or coalition.side.BLUE -return self -end -function PLAYERTASK:GetCoalition() -self:T(self.lid.."GetCoalition") -return self.coalition -end -function PLAYERTASK:GetTarget() -self:T(self.lid.."GetTarget") -return self.Target -end -function PLAYERTASK:AddFreetext(Text) -self:T(self.lid.."AddFreetext") -self.Freetext=Text -return self -end -function PLAYERTASK:HasFreetext() -self:T(self.lid.."HasFreetext") -return self.Freetext~=nil and true or false -end -function PLAYERTASK:HasFreetextTTS() -self:T(self.lid.."HasFreetextTTS") -return self.FreetextTTS~=nil and true or false -end -function PLAYERTASK:SetSubType(Type) -self:T(self.lid.."AddSubType") -self.TaskSubType=Type -return self -end -function PLAYERTASK:GetSubType() -self:T(self.lid.."GetSubType") -return self.TaskSubType -end -function PLAYERTASK:GetFreetext() -self:T(self.lid.."GetFreetext") -return self.Freetext or self.FreetextTTS or"No Details" -end -function PLAYERTASK:AddFreetextTTS(TextTTS) -self:T(self.lid.."AddFreetextTTS") -self.FreetextTTS=TextTTS -return self -end -function PLAYERTASK:GetFreetextTTS() -self:T(self.lid.."GetFreetextTTS") -return self.FreetextTTS or self.Freetext or"No Details" -end -function PLAYERTASK:SetMenuName(Text) -self:T(self.lid.."SetMenuName") -self.Target.name=Text -return self -end -function PLAYERTASK:AddNextTaskAfterSuccess(Task) -self:T(self.lid.."AddNextTaskAfterSuccess") -table.insert(self.NextTaskSuccess,Task) -return self -end -function PLAYERTASK:AddNextTaskAfterFailure(Task) -self:T(self.lid.."AddNextTaskAfterFailure") -table.insert(self.NextTaskFailure,Task) -return self -end -function PLAYERTASK:IsDone() -self:T(self.lid.."IsDone?") -local IsDone=false -local state=self:GetState() -if state=="Done"or state=="Stopped"then -IsDone=true -end -return IsDone -end -function PLAYERTASK:GetClients() -self:T(self.lid.."GetClients") -local clientlist=self.Clients:GetIDStackSorted()or{} -local count=self.Clients:Count() -return clientlist,count -end -function PLAYERTASK:GetClientObjects() -self:T(self.lid.."GetClientObjects") -local clientlist=self.Clients:GetDataTable()or{} -local count=self.Clients:Count() -return clientlist,count -end -function PLAYERTASK:CountClients() -self:T(self.lid.."CountClients") -return self.Clients:Count() -end -function PLAYERTASK:HasPlayerName(Name) -self:T(self.lid.."HasPlayerName?") -return self.Clients:HasUniqueID(Name) -end -function PLAYERTASK:AddClient(Client) -self:T(self.lid.."AddClient") -local name=Client:GetPlayerName() -if not self.Clients:HasUniqueID(name)then -self.Clients:Push(Client,name) -self:__ClientAdded(-2,Client) -end -if self.TaskController and self.TaskController.Scoring then -self.TaskController.Scoring:_AddPlayerFromUnit(Client) -end -return self -end -function PLAYERTASK:RemoveClient(Client,Name) -self:T(self.lid.."RemoveClient") -local name=Name or Client:GetPlayerName() -if self.Clients:HasUniqueID(name)then -self.Clients:PullByID(name) -if self.verbose then -self.Clients:Flush() -end -self:__ClientRemoved(-2,Client) -if self.Clients:Count()==0 then -self:__Failed(-1) -end -end -return self -end -function PLAYERTASK:ClientAbort(Client) -self:T(self.lid.."ClientAbort") -if Client and Client:IsAlive()then -self:RemoveClient(Client) -self:__ClientAborted(-1,Client) -return self -else -if self.Clients:Count()==0 then -self:__Failed(-1) -end -end -return self -end -function PLAYERTASK:MarkTargetOnF10Map(Text,Coalition,ReadOnly) -self:T(self.lid.."MarkTargetOnF10Map") -if self.Target then -local coordinate=self.Target:GetCoordinate() -if coordinate then -if self.TargetMarker then -self.TargetMarker:Remove() -end -local text=Text or("Target of "..self.lid) -self.TargetMarker=MARKER:New(coordinate,text) -if ReadOnly then -self.TargetMarker:ReadOnly() -end -if Coalition then -self.TargetMarker:ToCoalition(Coalition) -else -self.TargetMarker:ToAll() -end -end -end -return self -end -function PLAYERTASK:SmokeTarget(Color) -self:T(self.lid.."SmokeTarget") -local color=Color or SMOKECOLOR.Red -if not self.lastsmoketime then self.lastsmoketime=0 end -local TDiff=timer.getAbsTime()-self.lastsmoketime -if self.Target and TDiff>299 then -local coordinate=self.Target:GetAverageCoordinate() -if coordinate then -coordinate:Smoke(color) -self.lastsmoketime=timer.getAbsTime() -end -end -return self -end -function PLAYERTASK:FlareTarget(Color) -self:T(self.lid.."SmokeTarget") -local color=Color or FLARECOLOR.Red -if self.Target then -local coordinate=self.Target:GetAverageCoordinate() -if coordinate then -coordinate:Flare(color,0) -end -end -return self -end -function PLAYERTASK:IlluminateTarget(Power,Height) -self:T(self.lid.."IlluminateTarget") -local Power=Power or 1000 -local Height=Height or 150 -if self.Target then -local coordinate=self.Target:GetAverageCoordinate() -if coordinate then -local bcoord=COORDINATE:NewFromVec2(coordinate:GetVec2(),Height) -bcoord:IlluminationBomb(Power) -end -end -return self -end -function PLAYERTASK:AddConditionSuccess(ConditionFunction,...) -local condition={} -condition.func=ConditionFunction -condition.arg={} -if arg then -condition.arg=arg -end -table.insert(self.conditionSuccess,condition) -return self -end -function PLAYERTASK:AddConditionFailure(ConditionFunction,...) -local condition={} -condition.func=ConditionFunction -condition.arg={} -if arg then -condition.arg=arg -end -table.insert(self.conditionFailure,condition) -return self -end -function PLAYERTASK:_EvalConditionsAny(Conditions) -for _,_condition in pairs(Conditions or{})do -local condition=_condition -local istrue=condition.func(unpack(condition.arg)) -if istrue then -return true -end -end -return false -end -function PLAYERTASK:onafterStatus(From,Event,To) -self:T({From,Event,To}) -self:T(self.lid.."onafterStatus") -local status=self:GetState() -if status=="Stopped"then return self end -local targetdead=false -if self.Type~=AUFTRAG.Type.CTLD and self.Type~=AUFTRAG.Type.CSAR then -if self.Target:IsDead()or self.Target:IsDestroyed()or self.Target:CountTargets()==0 then -targetdead=true -self:__Success(-2) -status="Success" -return self -end -end -local clientsalive=false -if status=="Executing"then -local ClientTable=self.Clients:GetDataTable() -for _,_client in pairs(ClientTable)do -local client=_client -if client:IsAlive()then -clientsalive=true -end -end -if status=="Executing"and(not clientsalive)and(not targetdead)then -self:__Failed(-2) -status="Failed" -end -end -if status~="Done"and status~="Stopped"then -local successCondition=self:_EvalConditionsAny(self.conditionSuccess) -local failureCondition=self:_EvalConditionsAny(self.conditionFailure) -if failureCondition and status~="Failed"then -self:__Failed(-2) -status="Failed" -elseif successCondition then -self:__Success(-2) -status="Success" -end -if status~="Failed"and status~="Success"then -local targetcount=self.Target:CountTargets() -if targetcount0 then -for _,_client in pairs(clients)do -self.TaskController.Scoring:AddGoalScore(_client,self.Type,nil,10) -end -end -end -self.TaskController:__TaskProgress(-1,self,TargetCount) -end -return self -end -function PLAYERTASK:onafterPlanned(From,Event,To) -self:T({From,Event,To}) -self.timestamp=timer.getAbsTime() -return self -end -function PLAYERTASK:onafterRequested(From,Event,To) -self:T({From,Event,To}) -self.timestamp=timer.getAbsTime() -return self -end -function PLAYERTASK:onafterExecuting(From,Event,To) -self:T({From,Event,To}) -self.timestamp=timer.getAbsTime() -return self -end -function PLAYERTASK:onafterStop(From,Event,To) -self:T({From,Event,To}) -self.timestamp=timer.getAbsTime() -return self -end -function PLAYERTASK:onafterClientAdded(From,Event,To,Client) -self:T({From,Event,To}) -if Client and self.verbose then -local text=string.format("Player %s joined task %03d!",Client:GetPlayerName()or"Generic",self.PlayerTaskNr) -self:T(self.lid..text) -end -self.timestamp=timer.getAbsTime() -return self -end -function PLAYERTASK:onafterDone(From,Event,To) -self:T({From,Event,To}) -if self.TaskController then -self.TaskController:__TaskDone(-1,self) -end -self.timestamp=timer.getAbsTime() -self:__Stop(-1) -return self -end -function PLAYERTASK:onafterCancel(From,Event,To) -self:T({From,Event,To}) -if self.TaskController then -self.TaskController:__TaskCancelled(-1,self) -end -self.timestamp=timer.getAbsTime() -self.FinalState="Cancel" -self:__Done(-1) -return self -end -function PLAYERTASK:onafterSuccess(From,Event,To) -self:T({From,Event,To}) -if self.TaskController then -self.TaskController:__TaskSuccess(-1,self) -end -if self.TargetMarker then -self.TargetMarker:Remove() -end -if self.TaskController.Scoring then -local clients,count=self:GetClientObjects() -if count>0 then -for _,_client in pairs(clients)do -local auftrag=self:GetSubType() -self.TaskController.Scoring:AddGoalScore(_client,self.Type,nil,self.TaskController.Scores[self.Type]) -end -end -end -self.timestamp=timer.getAbsTime() -self.FinalState="Success" -self:__Done(-1) -return self -end -function PLAYERTASK:onafterFailed(From,Event,To) -self:T({From,Event,To}) -self.repeats=self.repeats+1 -if self.Repeat and(self.repeats<=self.RepeatNo)then -if self.TaskController then -self.TaskController:__TaskRepeatOnFailed(-1,self) -end -self:__Planned(-1) -return self -else -if self.TargetMarker then -self.TargetMarker:Remove() -end -self.FinalState="Failed" -self:__Done(-1) -end -if self.TaskController.Scoring then -local clients,count=self:GetClientObjects() -if count>0 then -for _,_client in pairs(clients)do -local auftrag=self:GetSubType() -self.TaskController.Scoring:AddGoalScore(_client,self.Type,nil,-self.TaskController.Scores[self.Type]) -end -end -end -self.timestamp=timer.getAbsTime() -return self -end -end -do -PLAYERTASKCONTROLLER={ -ClassName="PLAYERTASKCONTROLLER", -verbose=false, -lid=nil, -TargetQueue=nil, -ClientSet=nil, -UseGroupNames=true, -PlayerMenu={}, -usecluster=false, -MenuName=nil, -ClusterRadius=0.5, -NoScreenOutput=false, -TargetRadius=500, -UseWhiteList=false, -WhiteList={}, -gettext=nil, -locale="en", -precisionbombing=false, -taskinfomenu=false, -activehasinfomenu=false, -MarkerReadOnly=false, -customcallsigns={}, -ShortCallsign=true, -Keepnumber=false, -CallsignTranslations=nil, -PlayerFlashMenu={}, -PlayerJoinMenu={}, -PlayerInfoMenu={}, -PlayerMenuTag={}, -noflaresmokemenu=false, -illumenu=false, -TransmitOnlyWithPlayers=true, -buddylasing=false, -PlayerRecce=nil, -Coalition=nil, -MenuParent=nil, -ShowMagnetic=true, -InfoHasLLDDM=false, -InfoHasCoordinate=false, -UseTypeNames=false, -Scoring=nil, -MenuNoTask=nil, -} -PLAYERTASKCONTROLLER.Type={ -A2A="Air-To-Air", -A2G="Air-To-Ground", -A2S="Air-To-Sea", -A2GS="Air-To-Ground-Sea", -} -AUFTRAG.Type.PRECISIONBOMBING="Precision Bombing" -AUFTRAG.Type.CTLD="Combat Transport" -AUFTRAG.Type.CSAR="Combat Rescue" -PLAYERTASKCONTROLLER.Scores={ -[AUFTRAG.Type.PRECISIONBOMBING]=100, -[AUFTRAG.Type.CTLD]=100, -[AUFTRAG.Type.CSAR]=100, -[AUFTRAG.Type.INTERCEPT]=100, -[AUFTRAG.Type.ANTISHIP]=100, -[AUFTRAG.Type.CAS]=100, -[AUFTRAG.Type.BAI]=100, -[AUFTRAG.Type.SEAD]=100, -[AUFTRAG.Type.BOMBING]=100, -[AUFTRAG.Type.BOMBRUNWAY]=100, -} -PLAYERTASKCONTROLLER.SeadAttributes={ -SAM=GROUP.Attribute.GROUND_SAM, -AAA=GROUP.Attribute.GROUND_AAA, -EWR=GROUP.Attribute.GROUND_EWR, -} -PLAYERTASKCONTROLLER.Messages={ -EN={ -TASKABORT="Task aborted!", -NOACTIVETASK="No active task!", -FREQUENCIES="frequencies ", -FREQUENCY="frequency %.3f", -BROADCAST="%s, %s, switch to %s for task assignment!", -CASTTS="close air support", -SEADTTS="suppress air defense", -BOMBTTS="bombing", -PRECBOMBTTS="precision bombing", -BAITTS="battle field air interdiction", -ANTISHIPTTS="anti-ship", -INTERCEPTTS="intercept", -BOMBRUNWAYTTS="bomb runway", -HAVEACTIVETASK="You already have one active task! Complete it first!", -PILOTJOINEDTASK="%s, %s. You have been assigned %s task %03d", -TASKNAME="%s Task ID %03d", -TASKNAMETTS="%s Task ID %03d", -THREATHIGH="high", -THREATMEDIUM="medium", -THREATLOW="low", -THREATTEXT="%s\nThreat: %s\nTargets left: %d\nCoord: %s", -THREATTEXTTTS="%s, %s. Target information for %s. Threat level %s. Targets left %d. Target location %s.", -MARKTASK="%s, %s, copy, task %03d location marked on map!", -SMOKETASK="%s, %s, copy, task %03d location smoked!", -FLARETASK="%s, %s, copy, task %03d location illuminated!", -ABORTTASK="All stations, %s, %s has aborted %s task %03d!", -UNKNOWN="Unknown", -MENUTASKING=" Tasking ", -MENUACTIVE="Active Task", -MENUINFO="Info", -MENUMARK="Mark on map", -MENUSMOKE="Smoke", -MENUFLARE="Flare", -MENUILLU="Illuminate", -MENUABORT="Abort", -MENUJOIN="Join Task", -MENUTASKINFO="Task Info", -MENUTASKNO="TaskNo", -MENUNOTASKS="Currently no tasks available.", -TASKCANCELLED="Task #%03d %s is cancelled!", -TASKCANCELLEDTTS="%s, task %03d %s is cancelled!", -TASKSUCCESS="Task #%03d %s completed successfully!", -TASKSUCCESSTTS="%s, task %03d %s completed successfully!", -TASKFAILED="Task #%03d %s was a failure!", -TASKFAILEDTTS="%s, task %03d %s was a failure!", -TASKFAILEDREPLAN="Task #%03d %s available for reassignment!", -TASKFAILEDREPLANTTS="%s, task %03d %s vailable for reassignment!", -TASKADDED="%s has a new %s task available!", -PILOTS="\nPilot(s): ", -PILOTSTTS=". Pilot(s): ", -YES="Yes", -NO="No", -NONE="None", -POINTEROVERTARGET="%s, %s, pointer in reach for task %03d, lasing!", -POINTERTARGETREPORT="\nPointer in reach: %s\nLasing: %s", -RECCETARGETREPORT="\nRecce %s in reach: %s\nLasing: %s", -POINTERTARGETLASINGTTS=". Pointer in reach and lasing.", -TARGET="Target", -FLASHON="%s - Flashing directions is now ON!", -FLASHOFF="%s - Flashing directions is now OFF!", -FLASHMENU="Flash Directions Switch", -BRIEFING="Briefing", -TARGETLOCATION="Target location", -COORDINATE="Coordinate", -INFANTRY="Infantry", -TECHNICAL="Technical", -ARTILLERY="Artillery", -TANKS="Tanks", -AIRDEFENSE="Airdefense", -SAM="SAM", -GROUP="Group", -UNARMEDSHIP="Merchant", -LIGHTARMEDSHIP="Light Boat", -CORVETTE="Corvette", -FRIGATE="Frigate", -CRUISER="Cruiser", -DESTROYER="Destroyer", -CARRIER="Aircraft Carrier", -}, -DE={ -TASKABORT="Auftrag abgebrochen!", -NOACTIVETASK="Kein aktiver Auftrag!", -FREQUENCIES="Frequenzen ", -FREQUENCY="Frequenz %.3f", -BROADCAST="%s, %s, Radio %s für Aufgabenzuteilung!", -CASTTS="Nahbereichsunterstützung", -SEADTTS="Luftabwehr ausschalten", -BOMBTTS="Bombardieren", -PRECBOMBTTS="Präzisionsbombardieren", -BAITTS="Luftunterstützung", -ANTISHIPTTS="Anti-Schiff", -INTERCEPTTS="Abfangen", -BOMBRUNWAYTTS="Startbahn Bombardieren", -HAVEACTIVETASK="Du hast einen aktiven Auftrag! Beende ihn zuerst!", -PILOTJOINEDTASK="%s, %s hat Auftrag %s %03d angenommen", -TASKNAME="%s Auftrag ID %03d", -TASKNAMETTS="%s Auftrag ID %03d", -THREATHIGH="hoch", -THREATMEDIUM="mittel", -THREATLOW="niedrig", -THREATTEXT="%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s", -THREATTEXTTTS="%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.", -MARKTASK="%s, %s, verstanden, Zielposition %03d auf der Karte markiert!", -SMOKETASK="%s, %s, verstanden, Zielposition %03d mit Rauch markiert!", -FLARETASK="%s, %s, verstanden, Zielposition %03d beleuchtet!", -ABORTTASK="%s, an alle, %s hat Auftrag %s %03d abgebrochen!", -UNKNOWN="Unbekannt", -MENUTASKING=" Aufträge ", -MENUACTIVE="Aktiver Auftrag", -MENUINFO="Information", -MENUMARK="Kartenmarkierung", -MENUSMOKE="Rauchgranate", -MENUFLARE="Leuchtgranate", -MENUILLU="Feldbeleuchtung", -MENUABORT="Abbrechen", -MENUJOIN="Auftrag annehmen", -MENUTASKINFO="Auftrag Briefing", -MENUTASKNO="AuftragsNr", -MENUNOTASKS="Momentan keine Aufträge verfügbar.", -TASKCANCELLED="Auftrag #%03d %s wurde beendet!", -TASKCANCELLEDTTS="%s, Auftrag %03d %s wurde beendet!", -TASKSUCCESS="Auftrag #%03d %s erfolgreich!", -TASKSUCCESSTTS="%s, Auftrag %03d %s erfolgreich!", -TASKFAILED="Auftrag #%03d %s gescheitert!", -TASKFAILEDTTS="%s, Auftrag %03d %s gescheitert!", -TASKFAILEDREPLAN="Auftrag #%03d %s gescheitert! Neuplanung!", -TASKFAILEDREPLANTTS="%s, Auftrag %03d %s gescheitert! Neuplanung!", -TASKADDED="%s hat einen neuen Auftrag %s erstellt!", -PILOTS="\nPilot(en): ", -PILOTSTTS=". Pilot(en): ", -YES="Ja", -NO="Nein", -NONE="Keine", -POINTEROVERTARGET="%s, %s, Marker im Zielbereich für %03d, Laser an!", -POINTERTARGETREPORT="\nMarker im Zielbereich: %s\nLaser an: %s", -RECCETARGETREPORT="\nSpäher % im Zielbereich: %s\nLasing: %s", -POINTERTARGETLASINGTTS=". Marker im Zielbereich, Laser is an.", -TARGET="Ziel", -FLASHON="%s - Richtungsangaben einblenden ist EIN!", -FLASHOFF="%s - Richtungsangaben einblenden ist AUS!", -FLASHMENU="Richtungsangaben Schalter", -BRIEFING="Briefing", -TARGETLOCATION="Zielposition", -COORDINATE="Koordinate", -INFANTRY="Infantrie", -TECHNICAL="Technische", -ARTILLERY="Artillerie", -TANKS="Panzer", -AIRDEFENSE="Flak", -SAM="Luftabwehr", -GROUP="Einheit", -UNARMEDSHIP="Handelsschiff", -LIGHTARMEDSHIP="Tender", -CORVETTE="Korvette", -FRIGATE="Fregatte", -CRUISER="Kreuzer", -DESTROYER="Zerstörer", -CARRIER="Flugzeugträger", -}, -} -PLAYERTASKCONTROLLER.version="0.1.64" -function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter) -local self=BASE:Inherit(self,FSM:New()) -self.Name=Name or"CentCom" -self.Coalition=Coalition or coalition.side.BLUE -self.CoalitionName=UTILS.GetCoalitionName(Coalition) -self.Type=Type or PLAYERTASKCONTROLLER.Type.A2G -self.usecluster=false -if self.Type==PLAYERTASKCONTROLLER.Type.A2A then -self.usecluster=true -end -self.ClusterRadius=0.5 -self.TargetRadius=500 -self.ClientFilter=ClientFilter -self.TargetQueue=FIFO:New() -self.TaskQueue=FIFO:New() -self.TasksPerPlayer=FIFO:New() -self.PrecisionTasks=FIFO:New() -self.FlashPlayer={} -self.AllowFlash=false -self.lasttaskcount=0 -self.taskinfomenu=false -self.activehasinfomenu=false -self.MenuName=nil -self.menuitemlimit=5 -self.holdmenutime=30 -self.MarkerReadOnly=false -self.repeatonfailed=true -self.repeattimes=5 -self.UseGroupNames=true -self.customcallsigns={} -self.ShortCallsign=true -self.Keepnumber=false -self.CallsignTranslations=nil -self.noflaresmokemenu=false -self.illumenu=false -self.ShowMagnetic=true -self.UseTypeNames=false -self.IsClientSet=false -if ClientFilter and type(ClientFilter)=="table"and ClientFilter.ClassName and ClientFilter.ClassName=="SET_CLIENT"then -self.ClientSet=ClientFilter -self.IsClientSet=true -end -if ClientFilter and not self.IsClientSet then -self.ClientSet=SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterPrefixes(ClientFilter):FilterStart() -elseif not self.IsClientSet then -self.ClientSet=SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterStart() -end -self.ActiveClientSet=SET_CLIENT:New() -self.lid=string.format("PlayerTaskController %s %s | ",self.Name,tostring(self.Type)) -self:_InitLocalization() -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("*","Status","*") -self:AddTransition("*","TaskAdded","*") -self:AddTransition("*","TaskDone","*") -self:AddTransition("*","TaskCancelled","*") -self:AddTransition("*","TaskSuccess","*") -self:AddTransition("*","TaskFailed","*") -self:AddTransition("*","TaskProgress","*") -self:AddTransition("*","TaskTargetSmoked","*") -self:AddTransition("*","TaskTargetFlared","*") -self:AddTransition("*","TaskTargetIlluminated","*") -self:AddTransition("*","TaskRepeatOnFailed","*") -self:AddTransition("*","PlayerJoinedTask","*") -self:AddTransition("*","PlayerAbortedTask","*") -self:AddTransition("*","Stop","Stopped") -self:__Start(2) -local starttime=math.random(5,10) -self:__Status(starttime) -self:I(self.lid..self.version.." Started.") -return self -end -function PLAYERTASKCONTROLLER:EnableScoring(Scoring) -self.Scoring=Scoring or SCORING:New(self.Name) -return self -end -function PLAYERTASKCONTROLLER:DisableScoring() -self.Scoring=nil -return self -end -function PLAYERTASKCONTROLLER:_InitLocalization() -self:T(self.lid.."_InitLocalization") -self.gettext=TEXTANDSOUND:New("PLAYERTASKCONTROLLER","en") -self.locale="en" -for locale,table in pairs(self.Messages)do -local Locale=string.lower(tostring(locale)) -self:T("**** Adding locale: "..Locale) -for ID,Text in pairs(table)do -self:T(string.format('Adding ID %s',tostring(ID))) -self.gettext:AddEntry(Locale,tostring(ID),Text) -end -end -return self -end -function PLAYERTASKCONTROLLER:SetEnableUseTypeNames() -self:T(self.lid.."SetEnableUseTypeNames") -self.UseTypeNames=true -return self -end -function PLAYERTASKCONTROLLER:SetDisableUseTypeNames() -self:T(self.lid.."SetDisableUseTypeNames") -self.UseTypeNames=false -return self -end -function PLAYERTASKCONTROLLER:SetAllowFlashDirection(OnOff) -self:T(self.lid.."SetAllowFlashDirection") -self.AllowFlash=OnOff -return self -end -function PLAYERTASKCONTROLLER:SetDisableSmokeFlareTask() -self:T(self.lid.."SetDisableSmokeFlareTask") -self.noflaresmokemenu=true -return self -end -function PLAYERTASKCONTROLLER:SetTransmitOnlyWithPlayers(Switch) -self.TransmitOnlyWithPlayers=Switch -if self.SRSQueue then -self.SRSQueue:SetTransmitOnlyWithPlayers(Switch) -end -return self -end -function PLAYERTASKCONTROLLER:SetEnableSmokeFlareTask() -self:T(self.lid.."SetEnableSmokeFlareTask") -self.noflaresmokemenu=false -return self -end -function PLAYERTASKCONTROLLER:SetEnableIlluminateTask() -self:T(self.lid.."SetEnableSmokeFlareTask") -self.illumenu=true -return self -end -function PLAYERTASKCONTROLLER:SetDisableIlluminateTask() -self:T(self.lid.."SetDisableIlluminateTask") -self.illumenu=false -return self -end -function PLAYERTASKCONTROLLER:SetInfoShowsCoordinate(OnOff,LLDDM) -self:T(self.lid.."SetInfoShowsCoordinate") -self.InfoHasCoordinate=OnOff -self.InfoHasLLDDM=LLDDM -return self -end -function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) -if not ShortCallsign or ShortCallsign==false then -self.ShortCallsign=false -else -self.ShortCallsign=true -end -self.Keepnumber=Keepnumber or false -self.CallsignTranslations=CallsignTranslations -return self -end -function PLAYERTASKCONTROLLER:_GetTextForSpeech(text) -self:T(self.lid.."_GetTextForSpeech") -text=string.gsub(text,"%d","%1 ") -text=string.gsub(text,"^%s*","") -text=string.gsub(text,"%s*$","") -text=string.gsub(text," "," ") -return text -end -function PLAYERTASKCONTROLLER:SetTaskRepetition(OnOff,Repeats) -self:T(self.lid.."SetTaskRepetition") -if OnOff then -self.repeatonfailed=true -self.repeattimes=Repeats or 5 -else -self.repeatonfailed=false -self.repeattimes=Repeats or 5 -end -return self -end -function PLAYERTASKCONTROLLER:_SendMessageToClients(Text,Seconds) -self:T(self.lid.."_SendMessageToClients") -local seconds=Seconds or 10 -self.ClientSet:ForEachClient( -function(Client) -local m=MESSAGE:New(Text,seconds,"Tasking"):ToClient(Client) -end -) -return self -end -function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed) -self:T(self.lid.."EnablePrecisionBombing") -if FlightGroup then -if FlightGroup.ClassName and(FlightGroup.ClassName=="FLIGHTGROUP"or FlightGroup.ClassName=="ARMYGROUP")then -self.LasingDrone=FlightGroup -self.LasingDrone.playertask={} -self.LasingDrone.playertask.busy=false -self.LasingDrone.playertask.id=0 -self.precisionbombing=true -self.LasingDrone:SetLaser(LaserCode) -self.LaserCode=LaserCode or 1688 -self.LasingDroneTemplate=self.LasingDrone:_GetTemplate(true) -self.LasingDroneAlt=Alt or 10000 -self.LasingDroneSpeed=Speed or 120 -if self.LasingDrone:IsFlightgroup()then -self.LasingDroneIsFlightgroup=true -local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(self.Coalition)) -if HoldingPoint then BullsCoordinate=HoldingPoint end -local Orbit=AUFTRAG:NewORBIT_CIRCLE(BullsCoordinate,self.LasingDroneAlt,self.LasingDroneSpeed) -self.LasingDrone:AddMission(Orbit) -elseif self.LasingDrone:IsArmygroup()then -self.LasingDroneIsArmygroup=true -local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(self.Coalition)) -if HoldingPoint then BullsCoordinate=HoldingPoint end -local Orbit=AUFTRAG:NewONGUARD(BullsCoordinate) -self.LasingDrone:AddMission(Orbit) -end -else -self:E(self.lid.."No FLIGHTGROUP object passed or FLIGHTGROUP is not alive!") -end -else -self.autolase=nil -self.precisionbombing=false -end -return self -end -function PLAYERTASKCONTROLLER:EnableBuddyLasing(Recce) -self:T(self.lid.."EnableBuddyLasing") -self.buddylasing=true -self.PlayerRecce=Recce -return self -end -function PLAYERTASKCONTROLLER:DisableBuddyLasing() -self:T(self.lid.."DisableBuddyLasing") -self.buddylasing=false -return self -end -function PLAYERTASKCONTROLLER:EnableMarkerOps(Tag) -self:T(self.lid.."EnableMarkerOps") -local tag=Tag or"TASK" -local MarkerOps=MARKEROPS_BASE:New(tag,{"Name","Text"},true) -local function Handler(Keywords,Coord,Text) -if self.verbose then -local m=MESSAGE:New(string.format("Target added from marker at: %s",Coord:ToStringA2G(nil,nil,self.ShowMagnetic)),15,"INFO"):ToAll() -local m=MESSAGE:New(string.format("Text: %s",Text),15,"INFO"):ToAll() -end -local menuname=string.match(Text,"Name=(.+),") -local freetext=string.match(Text,"Text=(.+)") -if menuname then -Coord.menuname=menuname -if freetext then -Coord.freetext=freetext -end -end -self:AddTarget(Coord) -end -function MarkerOps:OnAfterMarkAdded(From,Event,To,Text,Keywords,Coord) -Handler(Keywords,Coord,Text) -end -function MarkerOps:OnAfterMarkChanged(From,Event,To,Text,Keywords,Coord) -Handler(Keywords,Coord,Text) -end -self.MarkerOps=MarkerOps -return self -end -function PLAYERTASKCONTROLLER:_GetPlayerName(Client) -self:T(self.lid.."_GetPlayerName") -local playername=Client:GetPlayerName() -local ttsplayername=nil -if not self.customcallsigns[playername]then -local playergroup=Client:GetGroup() -ttsplayername=playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) -local newplayername=self:_GetTextForSpeech(ttsplayername) -self.customcallsigns[playername]=newplayername -ttsplayername=newplayername -else -ttsplayername=self.customcallsigns[playername] -end -return playername,ttsplayername -end -function PLAYERTASKCONTROLLER:DisablePrecisionBombing(FlightGroup,LaserCode) -self:T(self.lid.."DisablePrecisionBombing") -self.autolase=nil -self.precisionbombing=false -return self -end -function PLAYERTASKCONTROLLER:EnableTaskInfoMenu() -self:T(self.lid.."EnableTaskInfoMenu") -self.taskinfomenu=true -return self -end -function PLAYERTASKCONTROLLER:DisableTaskInfoMenu() -self:T(self.lid.."DisableTaskInfoMenu") -self.taskinfomenu=false -return self -end -function PLAYERTASKCONTROLLER:SetMenuOptions(InfoMenu,ItemLimit,HoldTime) -self:T(self.lid.."SetMenuOptions") -self.activehasinfomenu=InfoMenu or false -if self.activehasinfomenu then -self:EnableTaskInfoMenu() -end -self.menuitemlimit=ItemLimit or 5 -self.holdmenutime=HoldTime or 30 -return self -end -function PLAYERTASKCONTROLLER:SetMarkerReadOnly() -self:T(self.lid.."SetMarkerReadOnly") -self.MarkerReadOnly=true -return self -end -function PLAYERTASKCONTROLLER:SetMarkerDeleteable() -self:T(self.lid.."SetMarkerDeleteable") -self.MarkerReadOnly=false -return self -end -function PLAYERTASKCONTROLLER:_EventHandler(EventData) -self:T(self.lid.."_EventHandler: "..EventData.id) -if EventData.id==EVENTS.PlayerLeaveUnit or EventData.id==EVENTS.Ejection or EventData.id==EVENTS.Crash or EventData.id==EVENTS.PilotDead then -if EventData.IniPlayerName then -self:T(self.lid.."Event for player: "..EventData.IniPlayerName) -local text="" -if self.TasksPerPlayer:HasUniqueID(EventData.IniPlayerName)then -local task=self.TasksPerPlayer:PullByID(EventData.IniPlayerName) -local Client=_DATABASE:FindClient(EventData.IniPlayerName) -if Client then -task:RemoveClient(Client) -text=self.gettext:GetEntry("TASKABORT",self.locale) -self.ActiveTaskMenuTemplate:ResetMenu(Client) -self.JoinTaskMenuTemplate:ResetMenu(Client) -else -task:RemoveClient(nil,EventData.IniPlayerName) -text=self.gettext:GetEntry("TASKABORT",self.locale) -end -else -text=self.gettext:GetEntry("NOACTIVETASK",self.locale) -end -self:T(self.lid..text) -end -elseif EventData.id==EVENTS.PlayerEnterAircraft and EventData.IniCoalition==self.Coalition then -if EventData.IniPlayerName and EventData.IniGroup then -if self.IsClientSet and(not self.ClientSet:IsIncludeObject(CLIENT:FindByName(EventData.IniUnitName)))then -self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName) -return self -end -self:T(self.lid.."Event for player: "..EventData.IniPlayerName) -if self.UseSRS then -local frequency=self.Frequency -local freqtext="" -if type(frequency)=="table"then -freqtext=self.gettext:GetEntry("FREQUENCIES",self.locale) -freqtext=freqtext..table.concat(frequency,", ") -else -local freqt=self.gettext:GetEntry("FREQUENCY",self.locale) -freqtext=string.format(freqt,frequency) -end -local modulation=self.Modulation -if type(modulation)=="table"then modulation=modulation[1]end -modulation=UTILS.GetModulationName(modulation) -local switchtext=self.gettext:GetEntry("BROADCAST",self.locale) -local playername=EventData.IniPlayerName -if EventData.IniGroup then -if self.customcallsigns[playername]then -self.customcallsigns[playername]=nil -end -playername=EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber) -end -playername=self:_GetTextForSpeech(playername) -local text=string.format(switchtext,playername,self.MenuName or self.Name,freqtext) -self.SRSQueue:NewTransmission(text,nil,self.SRS,timer.getAbsTime()+60,2,{EventData.IniGroup},text,30,self.BCFrequency,self.BCModulation) -end -if EventData.IniPlayerName then -local player=_DATABASE:FindClient(EventData.IniUnitName) -self:_SwitchMenuForClient(player,"Info") -end -end -end -return self -end -function PLAYERTASKCONTROLLER:SetLocale(Locale) -self:T(self.lid.."SetLocale") -self.locale=Locale or"en" -return self -end -function PLAYERTASKCONTROLLER:SuppressScreenOutput(OnOff) -self:T(self.lid.."SuppressScreenOutput") -self.NoScreenOutput=OnOff or false -return self -end -function PLAYERTASKCONTROLLER:SetTargetRadius(Radius) -self:T(self.lid.."SetTargetRadius") -self.TargetRadius=Radius or 500 -return self -end -function PLAYERTASKCONTROLLER:SetClusterRadius(Radius) -self:T(self.lid.."SetClusterRadius") -self.ClusterRadius=Radius or 0.5 -self.usecluster=true -return self -end -function PLAYERTASKCONTROLLER:CancelTask(Task) -self:T(self.lid.."CancelTask") -Task:__Cancel(-1) -return self -end -function PLAYERTASKCONTROLLER:SwitchUseGroupNames(OnOff) -self:T(self.lid.."SwitchUseGroupNames") -if OnOff then -self.UseGroupNames=true -else -self.UseGroupNames=false -end -return self -end -function PLAYERTASKCONTROLLER:SwitchMagenticAngles(OnOff) -self:T(self.lid.."SwitchMagenticAngles") -if OnOff then -self.ShowMagnetic=true -else -self.ShowMagnetic=false -end -return self -end -function PLAYERTASKCONTROLLER:_GetAvailableTaskTypes() -self:T(self.lid.."_GetAvailableTaskTypes") -local tasktypes={} -self.TaskQueue:ForEach( -function(Task) -local task=Task -local type=Task.Type -tasktypes[type]={} -end -) -return tasktypes -end -function PLAYERTASKCONTROLLER:_GetTasksPerType() -self:T(self.lid.."_GetTasksPerType") -local tasktypes=self:_GetAvailableTaskTypes() -local datatable=self.TaskQueue:GetDataTable() -local threattable={} -for _,_task in pairs(datatable)do -local task=_task -local threat=task.Target:GetThreatLevelMax() -if not task:IsDone()then -threattable[#threattable+1]={task=task,threat=threat} -end -end -table.sort(threattable,function(k1,k2)return k1.threat>k2.threat end) -for _id,_data in pairs(threattable)do -local threat=_data.threat -local task=_data.task -local type=task.Type -local name=task.Target:GetName() -if not task:IsDone()then -table.insert(tasktypes[type],task) -end -end -return tasktypes -end -function PLAYERTASKCONTROLLER:_CheckTargetQueue() -self:T(self.lid.."_CheckTargetQueue") -if self.TargetQueue:Count()>0 then -local object=self.TargetQueue:Pull() -local target=TARGET:New(object) -if object.menuname then -target.menuname=object.menuname -if object.freetext then -target.freetext=object.freetext -end -end -if object:IsInstanceOf("UNIT")or object:IsInstanceOf("GROUP")then -if self.UseTypeNames and object:IsGround()then -local threat=object:GetThreatLevel() -local typekey="INFANTRY" -if threat==0 or threat==2 then -typekey="TECHNICAL" -elseif threat==3 then -typekey="ARTILLERY" -elseif threat==4 or threat==5 then -typekey="TANKS" -elseif threat==6 or threat==7 then -typekey="AIRDEFENSE" -elseif threat>=8 then -typekey="SAM" -end -local typename=self.gettext:GetEntry(typekey,self.locale) -local gname=self.gettext:GetEntry("GROUP",self.locale) -target.TypeName=string.format("%s %s",typename,gname) -end -if self.UseTypeNames and object:IsShip()then -local threat=object:GetThreatLevel() -local typekey="UNARMEDSHIP" -if threat==1 then -typekey="LIGHTARMEDSHIP" -elseif threat==2 then -typekey="CORVETTE" -elseif threat==3 or threat==4 then -typekey="FRIGATE" -elseif threat==5 or threat==6 then -typekey="CRUISER" -elseif threat==7 or threat==8 then -typekey="DESTROYER" -elseif threat>=9 then -typekey="CARRIER" -end -local typename=self.gettext:GetEntry(typekey,self.locale) -target.TypeName=typename -end -end -self:_AddTask(target) -end -return self -end -function PLAYERTASKCONTROLLER:_CheckTaskQueue() -self:T(self.lid.."_CheckTaskQueue") -if self.TaskQueue:Count()>0 then -local tasks=self.TaskQueue:GetIDStack() -for _id,_entry in pairs(tasks)do -local data=_entry.data -self:T("Looking at Task: "..data.PlayerTaskNr.." Type: "..data.Type.." State: "..data:GetState()) -if data:GetState()=="Done"or data:GetState()=="Stopped"then -local task=self.TaskQueue:ReadByID(_id) -local clientsattask=task.Clients:GetIDStackSorted() -for _,_id in pairs(clientsattask)do -self:T("*****Removing player ".._id) -self.TasksPerPlayer:PullByID(_id) -end -local clients=task:GetClientObjects() -for _,client in pairs(clients)do -self:_RemoveMenuEntriesForTask(task,client) -end -for _,client in pairs(clients)do -self:_SwitchMenuForClient(client,"Info",5) -end -local nexttasks={} -if task.FinalState=="Success"then -nexttasks=task.NextTaskSuccess -elseif task.FinalState=="Failed"then -nexttasks=task.NextTaskFailure -end -local clientlist,count=task:GetClientObjects() -if count>0 then -for _,_client in pairs(clientlist)do -local client=_client -local group=client:GetGroup() -for _,task in pairs(nexttasks)do -self:_JoinTask(task,true,group,client) -end -end -end -local TNow=timer.getAbsTime() -if TNow-task.timestamp>5 then -self:_RemoveMenuEntriesForTask(task) -local task=self.TaskQueue:PullByID(_id) -task=nil -end -end -end -end -return self -end -function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() -self:T(self.lid.."_CheckPrecisionTasks") -if self.PrecisionTasks:Count()>0 and self.precisionbombing then -if not self.LasingDrone or self.LasingDrone:IsDead()then -self:E(self.lid.."Lasing drone is dead ... creating a new one!") -if self.LasingDrone then -self.LasingDrone:_Respawn(1,nil,true) -else -if self.LasingDroneIsFlightgroup then -local FG=FLIGHTGROUP:New(self.LasingDroneTemplate) -FG:Activate() -self:EnablePrecisionBombing(FG,self.LaserCode or 1688) -else -local FG=ARMYGROUP:New(self.LasingDroneTemplate) -FG:Activate() -self:EnablePrecisionBombing(FG,self.LaserCode or 1688) -end -end -return self -end -if self.LasingDrone and self.LasingDrone:IsAlive()then -if self.LasingDrone.playertask and(not self.LasingDrone.playertask.busy)then -self:T(self.lid.."Sending lasing unit to target") -local task=self.PrecisionTasks:Pull() -self.LasingDrone.playertask.id=task.PlayerTaskNr -self.LasingDrone.playertask.busy=true -self.LasingDrone.playertask.inreach=false -self.LasingDrone.playertask.reachmessage=false -if self.LasingDroneIsFlightgroup then -self.LasingDrone:CancelAllMissions() -local auftrag=AUFTRAG:NewORBIT_CIRCLE(task.Target:GetCoordinate(),self.LasingDroneAlt,self.LasingDroneSpeed) -self.LasingDrone:AddMission(auftrag) -elseif self.LasingDroneIsArmygroup then -local tgtcoord=task.Target:GetCoordinate() -local tgtzone=ZONE_RADIUS:New("ArmyGroup-"..math.random(1,10000),tgtcoord:GetVec2(),3000) -local finalpos=nil -for i=1,50 do -finalpos=tgtzone:GetRandomCoordinate(2500,0,{land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.SHALLOW_WATER}) -if finalpos then -if finalpos:IsLOS(tgtcoord,0)then -break -end -end -end -if finalpos then -self.LasingDrone:CancelAllMissions() -local auftrag=AUFTRAG:NewARMOREDGUARD(finalpos,"Off road") -self.LasingDrone:AddMission(auftrag) -else -self:E("***Could not find LOS position to post ArmyGroup for lasing!") -self.LasingDrone.playertask.id=0 -self.LasingDrone.playertask.busy=false -self.LasingDrone.playertask.inreach=false -self.LasingDrone.playertask.reachmessage=false -end -end -self.PrecisionTasks:Push(task,task.PlayerTaskNr) -elseif self.LasingDrone.playertask and self.LasingDrone.playertask.busy then -local task=self.PrecisionTasks:ReadByID(self.LasingDrone.playertask.id) -self:T("Looking at Task: "..task.PlayerTaskNr.." Type: "..task.Type.." State: "..task:GetState()) -if(not task)or task:GetState()=="Done"or task:GetState()=="Stopped"then -local task=self.PrecisionTasks:PullByID(self.LasingDrone.playertask.id) -self:_CheckTaskQueue() -task=nil -if self.LasingDrone:IsLasing()then -self.LasingDrone:__LaserOff(-1) -end -self.LasingDrone.playertask.busy=false -self.LasingDrone.playertask.inreach=false -self.LasingDrone.playertask.id=0 -self.LasingDrone.playertask.reachmessage=false -self:T(self.lid.."Laser Off") -else -local dcoord=self.LasingDrone:GetCoordinate() -local tcoord=task.Target:GetCoordinate() -tcoord.y=tcoord.y+2 -local dist=dcoord:Get2DDistance(tcoord) -if dist<3000 and not self.LasingDrone:IsLasing()then -self:T(self.lid.."Laser On") -self.LasingDrone:__LaserOn(-1,tcoord) -self.LasingDrone.playertask.inreach=true -if not self.LasingDrone.playertask.reachmessage then -self.LasingDrone.playertask.reachmessage=true -local clients=task:GetClients() -local text="" -for _,playername in pairs(clients)do -local pointertext=self.gettext:GetEntry("POINTEROVERTARGET",self.locale) -local ttsplayername=playername -if self.customcallsigns[playername]then -ttsplayername=self.customcallsigns[playername] -end -text=string.format(pointertext,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) -if not self.NoScreenOutput then -local client=nil -self.ClientSet:ForEachClient( -function(Client) -if Client:GetPlayerName()==playername then client=Client end -end -) -if client then -local m=MESSAGE:New(text,15,"Tasking"):ToClient(client) -end -end -end -if self.UseSRS then -self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) -end -end -end -end -end -end -end -return self -end -function PLAYERTASKCONTROLLER:_CheckPlayerHasTask(PlayerName) -self:T(self.lid.."_CheckPlayerHasTask") -return self.TasksPerPlayer:HasUniqueID(PlayerName) -end -function PLAYERTASKCONTROLLER:AddTarget(Target) -self:T(self.lid.."AddTarget") -self.TargetQueue:Push(Target) -return self -end -function PLAYERTASKCONTROLLER:_CheckTaskTypeAllowed(Type) -self:T(self.lid.."_CheckTaskTypeAllowed") -local Outcome=false -if self.UseWhiteList then -for _,_type in pairs(self.WhiteList)do -if Type==_type then -Outcome=true -break -end -end -else -return true -end -return Outcome -end -function PLAYERTASKCONTROLLER:_CheckTaskTypeDisallowed(Type) -self:T(self.lid.."_CheckTaskTypeDisallowed") -local Outcome=false -if self.UseBlackList then -for _,_type in pairs(self.BlackList)do -if Type==_type then -Outcome=true -break -end -end -else -return true -end -return Outcome -end -function PLAYERTASKCONTROLLER:SetTaskWhiteList(WhiteList) -self:T(self.lid.."SetTaskWhiteList") -self.WhiteList=WhiteList -self.UseWhiteList=true -return self -end -function PLAYERTASKCONTROLLER:SetTaskBlackList(BlackList) -self:T(self.lid.."SetTaskBlackList") -self.BlackList=BlackList -self.UseBlackList=true -return self -end -function PLAYERTASKCONTROLLER:SetSEADAttributes(Attributes) -self:T(self.lid.."SetSEADAttributes") -if type(Attributes)~="table"then -Attributes={Attributes} -end -self.SeadAttributes=Attributes -return self -end -function PLAYERTASKCONTROLLER:_IsAttributeSead(Attribute) -self:T(self.lid.."_IsAttributeSead?") -local IsSead=false -for _,_attribute in pairs(self.SeadAttributes)do -if Attribute==_attribute then -IsSead=true -break -end -end -return IsSead -end -function PLAYERTASKCONTROLLER:_AddTask(Target) -self:T(self.lid.."_AddTask") -local cat=Target:GetCategory() -local threat=Target:GetThreatLevelMax() -local type=AUFTRAG.Type.CAS -local ttstype=self.gettext:GetEntry("CASTTS",self.locale) -if cat==TARGET.Category.GROUND then -type=AUFTRAG.Type.CAS -local targetobject=Target:GetObject() -if targetobject:IsInstanceOf("UNIT")then -self:T("SEAD Check UNIT") -if targetobject:HasSEAD()then -type=AUFTRAG.Type.SEAD -ttstype=self.gettext:GetEntry("SEADTTS",self.locale) -end -elseif targetobject:IsInstanceOf("GROUP")then -self:T("SEAD Check GROUP") -local attribute=targetobject:GetAttribute() -if self:_IsAttributeSead(attribute)then -type=AUFTRAG.Type.SEAD -ttstype=self.gettext:GetEntry("SEADTTS",self.locale) -end -elseif targetobject:IsInstanceOf("SET_GROUP")then -self:T("SEAD Check SET_GROUP") -targetobject:ForEachGroup( -function(group) -local attribute=group:GetAttribute() -if self:_IsAttributeSead(attribute)then -type=AUFTRAG.Type.SEAD -ttstype=self.gettext:GetEntry("SEADTTS",self.locale) -end -end -) -elseif targetobject:IsInstanceOf("SET_UNIT")then -self:T("SEAD Check SET_UNIT") -targetobject:ForEachUnit( -function(unit) -if unit:HasSEAD()then -type=AUFTRAG.Type.SEAD -ttstype=self.gettext:GetEntry("SEADTTS",self.locale) -end -end -) -elseif targetobject:IsInstanceOf("SET_STATIC")or targetobject:IsInstanceOf("STATIC")then -self:T("(PRECISION-)BOMBING SET_STATIC or STATIC") -if self.precisionbombing then -type=AUFTRAG.Type.PRECISIONBOMBING -ttstype=self.gettext:GetEntry("PRECBOMBTTS",self.locale) -else -type=AUFTRAG.Type.BOMBING -ttstype=self.gettext:GetEntry("BOMBTTS",self.locale) -end -end -local targetcoord=Target:GetCoordinate() -local targetvec2=targetcoord:GetVec2() -local targetzone=ZONE_RADIUS:New(self.Name,targetvec2,self.TargetRadius) -local coalition=targetobject:GetCoalitionName()or"Blue" -coalition=string.lower(coalition) -self:T("Target coalition is "..tostring(coalition)) -local filtercoalition="blue" -if coalition=="blue"then filtercoalition="red"end -local friendlyset=SET_GROUP:New():FilterCategoryGround():FilterCoalitions(filtercoalition):FilterZones({targetzone}):FilterOnce() -if friendlyset:Count()==0 and type==AUFTRAG.Type.CAS then -type=AUFTRAG.Type.BAI -ttstype=self.gettext:GetEntry("BAITTS",self.locale) -end -if(type==AUFTRAG.Type.BAI or type==AUFTRAG.Type.CAS)and(self.precisionbombing or self.buddylasing)then -if threat>2 and threat<7 then -type=AUFTRAG.Type.PRECISIONBOMBING -ttstype=self.gettext:GetEntry("PRECBOMBTTS",self.locale) -end -end -elseif cat==TARGET.Category.NAVAL then -type=AUFTRAG.Type.ANTISHIP -ttstype=self.gettext:GetEntry("ANTISHIPTTS",self.locale) -elseif cat==TARGET.Category.AIRCRAFT then -type=AUFTRAG.Type.INTERCEPT -ttstype=self.gettext:GetEntry("INTERCEPTTS",self.locale) -elseif cat==TARGET.Category.AIRBASE then -type=AUFTRAG.Type.BOMBRUNWAY -ttstype=self.gettext:GetEntry("BOMBRUNWAYTTS",self.locale) -elseif cat==TARGET.Category.COORDINATE or cat==TARGET.Category.ZONE then -local zone=Target:GetObject() -if cat==TARGET.Category.COORDINATE then -zone=ZONE_RADIUS:New("TargetZone-"..math.random(1,10000),Target:GetVec2(),self.TargetRadius) -end -local enemies=self.CoalitionName=="Blue"and"red"or"blue" -local enemysetg=SET_GROUP:New():FilterCoalitions(enemies):FilterCategoryGround():FilterActive(true):FilterZones({zone}):FilterOnce() -local enemysets=SET_STATIC:New():FilterCoalitions(enemies):FilterZones({zone}):FilterOnce() -local countg=enemysetg:Count() -local counts=enemysets:Count() -if countg>0 then -if Target.menuname then -enemysetg.menuname=Target.menuname -if Target.freetext then -enemysetg.freetext=Target.freetext -end -end -self:AddTarget(enemysetg) -end -if counts>0 then -if Target.menuname then -enemysets.menuname=Target.menuname -if Target.freetext then -enemysets.freetext=Target.freetext -end -end -self:AddTarget(enemysets) -end -return self -end -if self.UseWhiteList then -if not self:_CheckTaskTypeAllowed(type)then -return self -end -end -if self.UseBlackList then -if self:_CheckTaskTypeDisallowed(type)then -return self -end -end -local task=PLAYERTASK:New(type,Target,self.repeatonfailed,self.repeattimes,ttstype) -if Target.menuname then -task:SetMenuName(Target.menuname) -if Target.freetext then -task:AddFreetext(Target.freetext) -end -end -task.coalition=self.Coalition -task.TypeName=Target.TypeName -if type==AUFTRAG.Type.BOMBRUNWAY then -task:HandleEvent(EVENTS.Shot) -function task:OnEventShot(EventData) -local data=EventData -local wcat=Object.getCategory(data.Weapon) -local coord=data.IniUnit:GetCoordinate()or data.IniGroup:GetCoordinate() -local vec2=coord:GetVec2()or{x=0,y=0} -local coal=data.IniCoalition -local afbzone=AIRBASE:FindByName(Target:GetName()):GetZone() -local runways=AIRBASE:FindByName(Target:GetName()):GetRunways()or{} -local inrunwayzone=false -for _,_runway in pairs(runways)do -local runway=_runway -if runway.zone:IsVec2InZone(vec2)then -inrunwayzone=true -end -end -local inzone=afbzone:IsVec2InZone(vec2) -if coal==task.coalition and(wcat==2 or wcat==3)and(inrunwayzone or inzone)then -task:__Success(-20) -end -end -end -task:_SetController(self) -self.TaskQueue:Push(task) -self:__TaskAdded(10,task) -return self -end -function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask,Silent,TaskFilter) -self:T(self.lid.."AddPlayerTaskToQueue") -if PlayerTask and PlayerTask.ClassName and PlayerTask.ClassName=="PLAYERTASK"then -if TaskFilter then -if self.UseWhiteList and(not self:_CheckTaskTypeAllowed(PlayerTask.Type))then -return self -end -if self.UseBlackList and self:_CheckTaskTypeDisallowed(PlayerTask.Type)then -return self -end -end -PlayerTask:_SetController(self) -PlayerTask:SetCoalition(self.Coalition) -self.TaskQueue:Push(PlayerTask) -if not Silent then -self:__TaskAdded(10,PlayerTask) -end -else -self:E(self.lid.."***** NO valid PAYERTASK object sent!") -end -return self -end -function PLAYERTASKCONTROLLER:_JoinTask(Task,Force,Group,Client) -self:T({Force,Group,Client}) -self:T(self.lid.."_JoinTask") -local force=false -if type(Force)=="boolean"then -force=Force -end -local playername,ttsplayername=self:_GetPlayerName(Client) -if self.TasksPerPlayer:HasUniqueID(playername)and not force then -if not self.NoScreenOutput then -local text=self.gettext:GetEntry("HAVEACTIVETASK",self.locale) -local m=MESSAGE:New(text,"10","Tasking"):ToClient(Client) -end -return self -end -local taskstate=Task:GetState() -if not Task:IsDone()then -if taskstate~="Executing"then -Task:__Requested(-1) -Task:__Executing(-2) -end -Task:AddClient(Client) -local joined=self.gettext:GetEntry("PILOTJOINEDTASK",self.locale) -local text=string.format(joined,ttsplayername,self.MenuName or self.Name,Task.TTSType,Task.PlayerTaskNr) -self:T(self.lid..text) -if not self.NoScreenOutput then -self:_SendMessageToClients(text) -end -if self.UseSRS then -self:T(self.lid..text) -self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) -end -self.TasksPerPlayer:Push(Task,playername) -self:__PlayerJoinedTask(1,Group,Client,Task) -self:_SwitchMenuForClient(Client,"Active",1) -end -if Task.Type==AUFTRAG.Type.PRECISIONBOMBING then -if not self.PrecisionTasks:HasUniqueID(Task.PlayerTaskNr)then -self.PrecisionTasks:Push(Task,Task.PlayerTaskNr) -end -end -return self -end -function PLAYERTASKCONTROLLER:_SwitchFlashing(Group,Client) -self:T(self.lid.."_SwitchFlashing") -local playername,ttsplayername=self:_GetPlayerName(Client) -if(not self.FlashPlayer[playername])or(self.FlashPlayer[playername]==false)then -self.FlashPlayer[playername]=Client -local flashtext=self.gettext:GetEntry("FLASHON",self.locale) -local text=string.format(flashtext,ttsplayername) -local m=MESSAGE:New(text,10,"Tasking"):ToClient(Client) -else -self.FlashPlayer[playername]=false -local flashtext=self.gettext:GetEntry("FLASHOFF",self.locale) -local text=string.format(flashtext,ttsplayername) -local m=MESSAGE:New(text,10,"Tasking"):ToClient(Client) -end -return self -end -function PLAYERTASKCONTROLLER:_FlashInfo() -self:T(self.lid.."_FlashInfo") -for _playername,_client in pairs(self.FlashPlayer)do -if _client and _client:IsAlive()then -if self.TasksPerPlayer:HasUniqueID(_playername)then -local task=self.TasksPerPlayer:ReadByID(_playername) -local Coordinate=task.Target:GetCoordinate() -local CoordText="" -if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then -CoordText=Coordinate:ToStringA2G(_client,nil,self.ShowMagnetic) -else -CoordText=Coordinate:ToStringA2A(_client,nil,self.ShowMagnetic) -end -local targettxt=self.gettext:GetEntry("TARGET",self.locale) -local text="Target: "..CoordText -local m=MESSAGE:New(text,10,"Tasking"):ToClient(_client) -end -end -end -return self -end -function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task,Group,Client) -self:T(self.lid.."_ActiveTaskInfo") -local playername,ttsplayername=self:_GetPlayerName(Client) -local text="" -local textTTS="" -local task=nil -if type(Task)~="string"then -task=Task -end -if self.TasksPerPlayer:HasUniqueID(playername)or task then -local task=task or self.TasksPerPlayer:ReadByID(playername) -local tname=self.gettext:GetEntry("TASKNAME",self.locale) -local ttsname=self.gettext:GetEntry("TASKNAMETTS",self.locale) -local taskname=string.format(tname,task.Type,task.PlayerTaskNr) -local ttstaskname=string.format(ttsname,task.TTSType,task.PlayerTaskNr) -local Coordinate=task.Target:GetCoordinate() -local CoordText="" -local CoordTextLLDM=nil -if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then -CoordText=Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) -else -CoordText=Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) -end -local ThreatLevel=task.Target:GetThreatLevelMax() -local ThreatLevelText=self.gettext:GetEntry("THREATHIGH",self.locale) -if ThreatLevel>3 and ThreatLevel<8 then -ThreatLevelText=self.gettext:GetEntry("THREATMEDIUM",self.locale) -elseif ThreatLevel<=3 then -ThreatLevelText=self.gettext:GetEntry("THREATLOW",self.locale) -end -local targets=task.Target:CountTargets()or 0 -local clientlist,clientcount=task:GetClients() -local ThreatGraph="["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]: "..ThreatLevel -local ThreatLocaleText=self.gettext:GetEntry("THREATTEXT",self.locale) -text=string.format(ThreatLocaleText,taskname,ThreatGraph,targets,CoordText) -if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then -if self.LasingDrone and self.LasingDrone.playertask then -local yes=self.gettext:GetEntry("YES",self.locale) -local no=self.gettext:GetEntry("NO",self.locale) -local inreach=self.LasingDrone.playertask.inreach==true and yes or no -local islasing=self.LasingDrone:IsLasing()==true and yes or no -local prectext=self.gettext:GetEntry("POINTERTARGETREPORT",self.locale) -prectext=string.format(prectext,inreach,islasing) -text=text..prectext.." ("..self.LaserCode..")" -end -end -if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.buddylasing then -if self.PlayerRecce then -local yes=self.gettext:GetEntry("YES",self.locale) -local no=self.gettext:GetEntry("NO",self.locale) -local reachdist=8000 -local inreach=false -local pset=self.PlayerRecce.PlayerSet:GetAliveSet() -for _,_player in pairs(pset)do -local player=_player -local pcoord=player:GetCoordinate() -if pcoord:Get2DDistance(Coordinate)<=reachdist then -inreach=true -local callsign=player:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) -local playername=player:GetPlayerName() -local islasing=no -if self.PlayerRecce.CanLase[player:GetTypeName()]and self.PlayerRecce.AutoLase[playername]then -islasing=yes -end -local inrtext=inreach==true and yes or no -local prectext=self.gettext:GetEntry("RECCETARGETREPORT",self.locale) -prectext=string.format(prectext,callsign,inrtext,islasing) -text=text..prectext -end -end -end -elseif task.Type==AUFTRAG.Type.CTLD or task.Type==AUFTRAG.Type.CSAR then -text=taskname -textTTS=taskname -local detail=task:GetFreetext() -local detailTTS=task:GetFreetextTTS() -local brieftxt=self.gettext:GetEntry("BRIEFING",self.locale) -local locatxt=self.gettext:GetEntry("TARGETLOCATION",self.locale) -text=text..string.format("\n%s: %s\n%s %s",brieftxt,detail,locatxt,CoordText) -textTTS=textTTS..string.format("; %s: %s; %s %s",brieftxt,detailTTS,locatxt,CoordText) -end -local clienttxt=self.gettext:GetEntry("PILOTS",self.locale) -if clientcount>0 then -for _,_name in pairs(clientlist)do -if self.customcallsigns[_name]then -_name=self.customcallsigns[_name] -_name=string.gsub(_name,"(%d) ","%1") -end -clienttxt=clienttxt.._name..", " -end -clienttxt=string.gsub(clienttxt,", $",".") -else -local keine=self.gettext:GetEntry("NONE",self.locale) -clienttxt=clienttxt..keine -end -text=text..clienttxt -textTTS=textTTS..clienttxt -if self.InfoHasCoordinate then -if self.InfoHasLLDDM then -CoordTextLLDM=Coordinate:ToStringLLDDM() -else -CoordTextLLDM=Coordinate:ToStringLLDMS() -end -local locatxt=self.gettext:GetEntry("COORDINATE",self.locale) -text=string.format("%s\n%s: %s",text,locatxt,CoordTextLLDM) -end -if task:HasFreetext()and not(task.Type==AUFTRAG.Type.CTLD or task.Type==AUFTRAG.Type.CSAR)then -local brieftxt=self.gettext:GetEntry("BRIEFING",self.locale) -text=text..string.format("\n%s: ",brieftxt)..task:GetFreetext() -end -if self.UseSRS then -if string.find(CoordText," BR, ")then -CoordText=string.gsub(CoordText," BR, "," Bee, Arr; ") -end -if self.ShowMagnetic then -text=string.gsub(text,"°M|","° magnetic; ") -end -if string.find(CoordText,"MGRS")then -local Text=string.gsub(CoordText,"MGRS ","") -Text=string.gsub(Text,"%s+","") -Text=string.gsub(Text,"([%a%d])","%1;") -Text=string.gsub(Text,"0","zero") -Text=string.gsub(Text,"9","niner") -CoordText="MGRS;"..Text -if self.PathToGoogleKey then -CoordText=string.format("%s",CoordText) -end -end -local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale) -local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) -if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then -if self.LasingDrone.playertask.inreach and self.LasingDrone:IsLasing()then -local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) -ttstext=ttstext..lasingtext -end -elseif task.Type==AUFTRAG.Type.CTLD or task.Type==AUFTRAG.Type.CSAR then -ttstext=textTTS -if string.find(ttstext," BR, ")then -CoordText=string.gsub(ttstext," BR, "," Bee, Arr, ") -end -elseif task:HasFreetext()then -local brieftxt=self.gettext:GetEntry("BRIEFING",self.locale) -ttstext=ttstext..string.format("; %s: ",brieftxt)..task:GetFreetextTTS() -end -self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,2) -end -else -text=self.gettext:GetEntry("NOACTIVETASK",self.locale) -end -if not self.NoScreenOutput then -local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) -end -return self -end -function PLAYERTASKCONTROLLER:_MarkTask(Group,Client) -self:T(self.lid.."_MarkTask") -local playername,ttsplayername=self:_GetPlayerName(Client) -local text="" -if self.TasksPerPlayer:HasUniqueID(playername)then -local task=self.TasksPerPlayer:ReadByID(playername) -text=string.format("Task ID #%03d | Type: %s | Threat: %d",task.PlayerTaskNr,task.Type,task.Target:GetThreatLevelMax()) -task:MarkTargetOnF10Map(text,self.Coalition,self.MarkerReadOnly) -local textmark=self.gettext:GetEntry("MARKTASK",self.locale) -text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) -self:T(self.lid..text) -if self.UseSRS then -self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) -end -else -text=self.gettext:GetEntry("NOACTIVETASK",self.locale) -end -if not self.NoScreenOutput then -local m=MESSAGE:New(text,"10","Tasking"):ToClient(Client) -end -return self -end -function PLAYERTASKCONTROLLER:_SmokeTask(Group,Client) -self:T(self.lid.."_SmokeTask") -local playername,ttsplayername=self:_GetPlayerName(Client) -local text="" -if self.TasksPerPlayer:HasUniqueID(playername)then -local task=self.TasksPerPlayer:ReadByID(playername) -task:SmokeTarget() -local textmark=self.gettext:GetEntry("SMOKETASK",self.locale) -text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) -self:T(self.lid..text) -if self.UseSRS then -self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) -end -self:__TaskTargetSmoked(5,task) -else -text=self.gettext:GetEntry("NOACTIVETASK",self.locale) -end -if not self.NoScreenOutput then -local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) -end -return self -end -function PLAYERTASKCONTROLLER:_FlareTask(Group,Client) -self:T(self.lid.."_FlareTask") -local playername,ttsplayername=self:_GetPlayerName(Client) -local text="" -if self.TasksPerPlayer:HasUniqueID(playername)then -local task=self.TasksPerPlayer:ReadByID(playername) -task:FlareTarget() -local textmark=self.gettext:GetEntry("FLARETASK",self.locale) -text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) -self:T(self.lid..text) -if self.UseSRS then -self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) -end -self:__TaskTargetFlared(5,task) -else -text=self.gettext:GetEntry("NOACTIVETASK",self.locale) -end -if not self.NoScreenOutput then -local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) -end -return self -end -function PLAYERTASKCONTROLLER:_IlluminateTask(Group,Client) -self:T(self.lid.."_IlluminateTask") -local playername,ttsplayername=self:_GetPlayerName(Client) -local text="" -if self.TasksPerPlayer:HasUniqueID(playername)then -local task=self.TasksPerPlayer:ReadByID(playername) -task:FlareTarget() -local textmark=self.gettext:GetEntry("FLARETASK",self.locale) -text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) -self:T(self.lid..text) -if self.UseSRS then -self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) -end -self:__TaskTargetIlluminated(5,task) -else -text=self.gettext:GetEntry("NOACTIVETASK",self.locale) -end -if not self.NoScreenOutput then -local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) -end -return self -end -function PLAYERTASKCONTROLLER:_AbortTask(Group,Client) -self:T(self.lid.."_AbortTask") -local playername,ttsplayername=self:_GetPlayerName(Client) -local text="" -if self.TasksPerPlayer:HasUniqueID(playername)then -local task=self.TasksPerPlayer:PullByID(playername) -task:ClientAbort(Client) -local textmark=self.gettext:GetEntry("ABORTTASK",self.locale) -text=string.format(textmark,self.MenuName or self.Name,ttsplayername,task.TTSType,task.PlayerTaskNr) -self:T(self.lid..text) -if self.UseSRS then -self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) -end -self:__PlayerAbortedTask(1,Group,Client,task) -else -text=self.gettext:GetEntry("NOACTIVETASK",self.locale) -end -if not self.NoScreenOutput then -local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) -end -self:_SwitchMenuForClient(Client,"Info",1) -return self -end -function PLAYERTASKCONTROLLER:_UpdateJoinMenuTemplate() -self:T("_UpdateJoinMenuTemplate") -if self.TaskQueue:Count()>0 then -local taskpertype=self:_GetTasksPerType() -local JoinMenu=self.JoinMenu -local controller=self.JoinTaskMenuTemplate -local actcontroller=self.ActiveTaskMenuTemplate -local actinfomenu=self.ActiveInfoMenu -if self.TaskQueue:Count()==0 and self.MenuNoTask==nil then -local menunotasks=self.gettext:GetEntry("MENUNOTASKS",self.locale) -self.MenuNoTask=controller:NewEntry(menunotasks,self.JoinMenu) -controller:AddEntry(self.MenuNoTask) -end -if self.TaskQueue:Count()>0 and self.MenuNoTask~=nil then -controller:DeleteGenericEntry(self.MenuNoTask) -controller:DeleteF10Entry(self.MenuNoTask) -self.MenuNoTask=nil -end -local maxn=self.menuitemlimit -for _type,_ in pairs(taskpertype)do -local found=controller:FindEntriesByText(_type) -if#found==0 then -local newentry=controller:NewEntry(_type,JoinMenu) -controller:AddEntry(newentry) -if self.JoinInfoMenu then -local newentry=controller:NewEntry(_type,self.JoinInfoMenu) -controller:AddEntry(newentry) -end -if actinfomenu then -local newentry=actcontroller:NewEntry(_type,self.ActiveInfoMenu) -actcontroller:AddEntry(newentry) -end -end -end -local typelist=self:_GetAvailableTaskTypes() -for _tasktype,_data in pairs(typelist)do -self:T("**** Building for TaskType: ".._tasktype) -for _,_task in pairs(taskpertype[_tasktype])do -_task=_task -self:T("**** Building for Task: ".._task.Target:GetName()) -if _task.InMenu then -self:T("**** Task already in Menu ".._task.Target:GetName()) -else -local menutaskno=self.gettext:GetEntry("MENUTASKNO",self.locale) -local text=string.format("%s %03d",menutaskno,_task.PlayerTaskNr) -if self.UseGroupNames then -local name=_task.Target:GetName() -if name~="Unknown"then -text=string.format("%s (%03d)",name,_task.PlayerTaskNr) -end -end -local parenttable,number=controller:FindEntriesByText(_tasktype,JoinMenu) -if number>0 then -local Parent=parenttable[1] -local matches,count=controller:FindEntriesByParent(Parent) -self:T("***** Join Menu ".._tasktype.." # of entries: "..count) -if count0 then -local Parent=parenttable[1] -local matches,count=controller:FindEntriesByParent(Parent) -self:T("***** Join Info Menu ".._tasktype.." # of entries: "..count) -if count0 then -local Parent=parenttable[1] -local matches,count=actcontroller:FindEntriesByParent(Parent) -self:T("***** Active Info Menu ".._tasktype.." # of entries: "..count) -if count0 and self.MenuNoTask~=nil then -JoinTaskMenuTemplate:DeleteGenericEntry(self.MenuNoTask) -self.MenuNoTask=nil -end -self.JoinTaskMenuTemplate=JoinTaskMenuTemplate -return self -end -function PLAYERTASKCONTROLLER:_CreateActiveTaskMenuTemplate() -self:T("_CreateActiveTaskMenuTemplate") -local menuactive=self.gettext:GetEntry("MENUACTIVE",self.locale) -local menuinfo=self.gettext:GetEntry("MENUINFO",self.locale) -local menumark=self.gettext:GetEntry("MENUMARK",self.locale) -local menusmoke=self.gettext:GetEntry("MENUSMOKE",self.locale) -local menuflare=self.gettext:GetEntry("MENUFLARE",self.locale) -local menuillu=self.gettext:GetEntry("MENUILLU",self.locale) -local menuabort=self.gettext:GetEntry("MENUABORT",self.locale) -local ActiveTaskMenuTemplate=CLIENTMENUMANAGER:New(self.ActiveClientSet,"ActiveTask") -if not self.ActiveTopMenu then -local taskings=self.gettext:GetEntry("MENUTASKING",self.locale) -local longname=self.Name..taskings..self.Type -local menuname=self.MenuName or longname -self.ActiveTopMenu=ActiveTaskMenuTemplate:NewEntry(menuname,self.MenuParent) -end -if self.AllowFlash then -local flashtext=self.gettext:GetEntry("FLASHMENU",self.locale) -ActiveTaskMenuTemplate:NewEntry(flashtext,self.ActiveTopMenu,self._SwitchFlashing,self) -end -local active=ActiveTaskMenuTemplate:NewEntry(menuactive,self.ActiveTopMenu) -ActiveTaskMenuTemplate:NewEntry(menuinfo,active,self._ActiveTaskInfo,self,"NONE") -ActiveTaskMenuTemplate:NewEntry(menumark,active,self._MarkTask,self) -if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and self.noflaresmokemenu~=true then -ActiveTaskMenuTemplate:NewEntry(menusmoke,active,self._SmokeTask,self) -ActiveTaskMenuTemplate:NewEntry(menuflare,active,self._FlareTask,self) -if self.illumenu then -ActiveTaskMenuTemplate:NewEntry(menuillu,active,self._IlluminateTask,self) -end -end -ActiveTaskMenuTemplate:NewEntry(menuabort,active,self._AbortTask,self) -self.ActiveTaskMenuTemplate=ActiveTaskMenuTemplate -if self.taskinfomenu and self.activehasinfomenu then -local menutaskinfo=self.gettext:GetEntry("MENUTASKINFO",self.locale) -self.ActiveInfoMenu=ActiveTaskMenuTemplate:NewEntry(menutaskinfo,self.ActiveTopMenu) -end -return self -end -function PLAYERTASKCONTROLLER:_SwitchMenuForClient(Client,MenuType,Delay) -self:T(self.lid.."_SwitchMenuForClient") -if Delay then -self:ScheduleOnce(Delay,self._SwitchMenuForClient,self,Client,MenuType) -return self -end -if MenuType=="Info"then -self.ClientSet:AddClientsByName(Client:GetName()) -self.ActiveClientSet:Remove(Client:GetName(),true) -self.ActiveTaskMenuTemplate:ResetMenu(Client) -self.JoinTaskMenuTemplate:ResetMenu(Client) -self.JoinTaskMenuTemplate:Propagate(Client) -elseif MenuType=="Active"then -self.ActiveClientSet:AddClientsByName(Client:GetName()) -self.ClientSet:Remove(Client:GetName(),true) -self.ActiveTaskMenuTemplate:ResetMenu(Client) -self.JoinTaskMenuTemplate:ResetMenu(Client) -self.ActiveTaskMenuTemplate:Propagate(Client) -else -self:E(self.lid.."Unknown menu type in _SwitchMenuForClient:"..tostring(MenuType)) -end -return self -end -function PLAYERTASKCONTROLLER:AddAgent(Recce) -self:T(self.lid.."AddAgent") -if self.Intel then -self.Intel:AddAgent(Recce) -else -self:E(self.lid.."*****NO detection has been set up (yet)!") -end -return self -end -function PLAYERTASKCONTROLLER:AddAgentSet(RecceSet) -self:T(self.lid.."AddAgentSet") -if self.Intel then -local Set=RecceSet:GetAliveSet() -for _,_Recce in pairs(Set)do -self.Intel:AddAgent(_Recce) -end -else -self:E(self.lid.."*****NO detection has been set up (yet)!") -end -return self -end -function PLAYERTASKCONTROLLER:SwitchDetectStatics(OnOff) -self:T(self.lid.."SwitchDetectStatics") -if self.Intel then -self.Intel:SetDetectStatics(OnOff) -else -self:E(self.lid.."***** NO detection has been set up (yet)!") -end -return self -end -function PLAYERTASKCONTROLLER:AddAcceptZone(AcceptZone) -self:T(self.lid.."AddAcceptZone") -if self.Intel then -self.Intel:AddAcceptZone(AcceptZone) -else -self:E(self.lid.."*****NO detection has been set up (yet)!") -end -return self -end -function PLAYERTASKCONTROLLER:AddAcceptZoneSet(AcceptZoneSet) -self:T(self.lid.."AddAcceptZoneSet") -if self.Intel then -self.Intel.acceptzoneset:AddSet(AcceptZoneSet) -else -self:E(self.lid.."*****NO detection has been set up (yet)!") -end -return self -end -function PLAYERTASKCONTROLLER:AddRejectZone(RejectZone) -self:T(self.lid.."AddRejectZone") -if self.Intel then -self.Intel:AddRejectZone(RejectZone) -else -self:E(self.lid.."*****NO detection has been set up (yet)!") -end -return self -end -function PLAYERTASKCONTROLLER:AddRejectZoneSet(RejectZoneSet) -self:T(self.lid.."AddRejectZoneSet") -if self.Intel then -self.Intel.rejectzoneset:AddSet(RejectZoneSet) -else -self:E(self.lid.."*****NO detection has been set up (yet)!") -end -return self -end -function PLAYERTASKCONTROLLER:RemoveAcceptZone(AcceptZone) -self:T(self.lid.."RemoveAcceptZone") -if self.Intel then -self.Intel:RemoveAcceptZone(AcceptZone) -else -self:E(self.lid.."*****NO detection has been set up (yet)!") -end -return self -end -function PLAYERTASKCONTROLLER:RemoveRejectZoneSet(RejectZone) -self:T(self.lid.."RemoveRejectZone") -if self.Intel then -self.Intel:RemoveRejectZone(RejectZone) -else -self:E(self.lid.."*****NO detection has been set up (yet)!") -end -return self -end -function PLAYERTASKCONTROLLER:SetMenuName(Name) -self:T(self.lid.."SetMenuName: "..Name) -self.MenuName=Name -return self -end -function PLAYERTASKCONTROLLER:SetParentMenu(Menu) -self:T(self.lid.."SetParentMenu") -return self -end -function PLAYERTASKCONTROLLER:SetupIntel(RecceName) -self:T(self.lid.."SetupIntel") -self.RecceSet=SET_GROUP:New():FilterCoalitions(self.CoalitionName):FilterPrefixes(RecceName):FilterStart() -self.Intel=INTEL:New(self.RecceSet,self.Coalition,self.Name.."-Intel") -self.Intel:SetClusterAnalysis(true,false,false) -self.Intel:SetClusterRadius(self.ClusterRadius or 0.5) -self.Intel.statusupdate=25 -self.Intel:SetAcceptZones() -self.Intel:SetRejectZones() -if self.Type==PLAYERTASKCONTROLLER.Type.A2G or self.Type==PLAYERTASKCONTROLLER.Type.A2GS then -self.Intel:SetDetectStatics(true) -end -self.Intel:__Start(2) -local function NewCluster(Cluster) -if not self.usecluster then return self end -local cluster=Cluster -local type=cluster.ctype -self:T({type,self.Type}) -if(type==INTEL.Ctype.AIRCRAFT and self.Type==PLAYERTASKCONTROLLER.Type.A2A)or(type==INTEL.Ctype.NAVAL and(self.Type==PLAYERTASKCONTROLLER.Type.A2S or self.Type==PLAYERTASKCONTROLLER.Type.A2GS))then -self:T("A2A or A2S") -local contacts=cluster.Contacts -local targetset=SET_GROUP:New() -for _,_object in pairs(contacts)do -local contact=_object -self:T("Adding group: "..contact.groupname) -targetset:AddGroup(contact.group,true) -end -self:AddTarget(targetset) -elseif(type==INTEL.Ctype.GROUND or type==INTEL.Ctype.STRUCTURE)and(self.Type==PLAYERTASKCONTROLLER.Type.A2G or self.Type==PLAYERTASKCONTROLLER.Type.A2GS)then -self:T("A2G") -local contacts=cluster.Contacts -local targetset=nil -if type==INTEL.Ctype.GROUND then -targetset=SET_GROUP:New() -for _,_object in pairs(contacts)do -local contact=_object -self:T("Adding group: "..contact.groupname) -targetset:AddGroup(contact.group,true) -end -elseif type==INTEL.Ctype.STRUCTURE then -targetset=SET_STATIC:New() -for _,_object in pairs(contacts)do -local contact=_object -self:T("Adding static: "..contact.groupname) -targetset:AddStatic(contact.group) -end -end -if targetset then -self:AddTarget(targetset) -end -end -end -local function NewContact(Contact) -if self.usecluster then return self end -local contact=Contact -local type=contact.ctype -self:T({type,self.Type}) -if(type==INTEL.Ctype.AIRCRAFT and self.Type==PLAYERTASKCONTROLLER.Type.A2A)or(type==INTEL.Ctype.NAVAL and(self.Type==PLAYERTASKCONTROLLER.Type.A2S or self.Type==PLAYERTASKCONTROLLER.Type.A2GS))then -self:T("A2A or A2S") -self:T("Adding group: "..contact.groupname) -self:AddTarget(contact.group) -elseif(type==INTEL.Ctype.GROUND or type==INTEL.Ctype.STRUCTURE)and(self.Type==PLAYERTASKCONTROLLER.Type.A2G or self.Type==PLAYERTASKCONTROLLER.Type.A2GS)then -self:T("A2G") -self:T("Adding group: "..contact.groupname) -self:AddTarget(contact.group) -end -end -function self.Intel:OnAfterNewCluster(From,Event,To,Cluster) -NewCluster(Cluster) -end -function self.Intel:OnAfterNewContact(From,Event,To,Contact) -NewContact(Contact) -end -return self -end -function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate) -self:T(self.lid.."SetSRS") -self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone" -self.Gender=Gender or MSRS.gender or"male" -self.Culture=Culture or MSRS.culture or"en-US" -self.Port=Port or MSRS.port or 5002 -self.Voice=Voice or MSRS.voice -self.PathToGoogleKey=PathToGoogleKey -self.AccessKey=AccessKey -self.Volume=Volume or 1.0 -self.UseSRS=true -self.Frequency=Frequency or{127,251} -self.BCFrequency=self.Frequency -self.Modulation=Modulation or{radio.modulation.FM,radio.modulation.AM} -self.BCModulation=self.Modulation -self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation) -self.SRS:SetCoalition(self.Coalition) -self.SRS:SetLabel(self.MenuName or self.Name) -self.SRS:SetGender(self.Gender) -self.SRS:SetCulture(self.Culture) -self.SRS:SetPort(self.Port) -self.SRS:SetVolume(self.Volume) -if self.PathToGoogleKey then -self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey) -self.SRS:SetProvider(MSRS.Provider.GOOGLE) -end -if(not PathToGoogleKey)and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then -self.PathToGoogleKey=MSRS.poptions.gcloud.credentials -self.Voice=Voice or MSRS.poptions.gcloud.voice -self.AccessKey=AccessKey or MSRS.poptions.gcloud.key -end -if Coordinate then -self.SRS:SetCoordinate(Coordinate) -end -self.SRS:SetVoice(self.Voice) -self.SRSQueue=MSRSQUEUE:New(self.MenuName or self.Name) -self.SRSQueue:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers) -return self -end -function PLAYERTASKCONTROLLER:SetSRSBroadcast(Frequency,Modulation) -self:T(self.lid.."SetSRSBroadcast") -if self.SRS then -self.BCFrequency=Frequency -self.BCModulation=Modulation -end -return self -end -function PLAYERTASKCONTROLLER:onafterStart(From,Event,To) -self:T({From,Event,To}) -self:T(self.lid.."onafterStart") -self:_CreateJoinMenuTemplate() -self:_CreateActiveTaskMenuTemplate() -self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler) -self:HandleEvent(EVENTS.Ejection,self._EventHandler) -self:HandleEvent(EVENTS.Crash,self._EventHandler) -self:HandleEvent(EVENTS.PilotDead,self._EventHandler) -self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) -self:SetEventPriority(5) -return self -end -function PLAYERTASKCONTROLLER:onafterStatus(From,Event,To) -self:T({From,Event,To}) -self:_CheckTargetQueue() -self:_CheckTaskQueue() -self:_CheckPrecisionTasks() -if self.AllowFlash then -self:_FlashInfo() -end -local targetcount=self.TargetQueue:Count() -local taskcount=self.TaskQueue:Count() -local playercount=self.ClientSet:CountAlive() -local assignedtasks=self.TasksPerPlayer:Count() -local enforcedmenu=false -if taskcount~=self.lasttaskcount then -self.lasttaskcount=taskcount -if taskcount12 then clock=clock-12 end -if clock==0 then clock=12 end -end -return clock -end -function PLAYERRECCE:SetLaserCodes(LaserCodes) -self.LaserCodes=(type(LaserCodes)=="table")and LaserCodes or{LaserCodes} -return self -end -function PLAYERRECCE:SetReferencePoint(Coordinate,Name) -self.ReferencePoint=Coordinate -self.RPName=Name -if self.RPMarker then -self.RPMarker:Remove() -end -local llddm=Coordinate:ToStringLLDDM() -local lldms=Coordinate:ToStringLLDMS() -local mgrs=Coordinate:ToStringMGRS() -local text=string.format("%s RP %s\n%s\n%s\n%s",self.Name,Name,llddm,lldms,mgrs) -self.RPMarker=MARKER:New(Coordinate,text) -self.RPMarker:ReadOnly() -self.RPMarker:ToCoalition(self.Coalition) -return self -end -function PLAYERRECCE:SetPlayerTaskController(Controller) -self.UseController=true -self.Controller=Controller -return self -end -function PLAYERRECCE:SetAttackSet(AttackSet) -self.AttackSet=AttackSet -return self -end -function PLAYERRECCE:_CameraOn(client,playername) -local camera=true -local unit=client -if unit and unit:IsAlive()then -local typename=unit:GetTypeName() -if string.find(typename,"SA342")then -local dcsunit=Unit.getByName(client:GetName()) -local vivihorizontal=dcsunit:getDrawArgumentValue(215)or 0 -if vivihorizontal<-0.7 or vivihorizontal>0.7 then -camera=false -end -elseif string.find(typename,"Ka-50")then -camera=true -end -end -return camera -end -function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle) -self:T(self.lid.."GetGazelleVivianneSight") -local unit=Gazelle -if unit and unit:IsAlive()then -local dcsunit=Unit.getByName(Gazelle:GetName()) -local vivihorizontal=dcsunit:getDrawArgumentValue(215)or 0 -local vivivertical=dcsunit:getDrawArgumentValue(216)or 0 -local vivioff=false -if vivihorizontal<-0.67 then -vivihorizontal=-0.67 -vivioff=false -elseif vivihorizontal>0.67 then -vivihorizontal=0.67 -vivioff=true -return 0,0,0,false -end -vivivertical=vivivertical/1.10731 -local horizontalview=vivihorizontal*-180 -local verticalview=vivivertical*30 -local heading=unit:GetHeading() -local viviheading=(heading+horizontalview)%360 -local maxview=self:_GetActualMaxLOSight(unit,viviheading,verticalview,vivioff) -local factor=3.15 -self.GazelleViewFactors={ -[1]=1.18, -[2]=1.32, -[3]=1.46, -[4]=1.62, -[5]=1.77, -[6]=1.85, -[7]=2.05, -[8]=2.05, -[9]=2.3, -[10]=2.3, -[11]=2.27, -[12]=2.27, -[13]=2.43, -} -local lfac=UTILS.Round(maxview,-2) -if lfac<=1300 then -factor=3.15 -maxview=math.ceil((maxview*factor)/100)*100 -end -if maxview>8000 then maxview=8000 end -return viviheading,verticalview,maxview,not vivioff -end -return 0,0,0,false -end -function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading,vnod,vivoff) -self:T(self.lid.."_GetActualMaxLOSight") -if vivoff then return 0 end -local maxview=0 -if unit and unit:IsAlive()then -local typename=unit:GetTypeName() -maxview=self.MaxViewDistance[typename]or 8000 -local CamHeight=self.Cameraheight[typename]or 0 -if vnod<0 then -local beta=90 -local gamma=math.floor(90-vnod) -local alpha=math.floor(180-beta-gamma) -local a=unit:GetHeight()-unit:GetCoordinate():GetLandHeight()+CamHeight -local b=a/math.sin(math.rad(alpha)) -local c=b*math.sin(math.rad(gamma)) -maxview=c*1.2 -end -end -return math.abs(maxview) -end -function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) -if not ShortCallsign or ShortCallsign==false then -self.ShortCallsign=false -else -self.ShortCallsign=true -end -self.Keepnumber=Keepnumber or false -self.CallsignTranslations=CallsignTranslations -return self -end -function PLAYERRECCE:_GetViewZone(unit,vheading,minview,maxview,angle,camon,laser) -self:T(self.lid.."_GetViewZone") -local viewzone=nil -if not camon then return nil end -if unit and unit:IsAlive()then -local unitname=unit:GetName() -if not laser then -local startpos=unit:GetCoordinate() -local heading1=(vheading+angle)%360 -local heading2=(vheading-angle)%360 -local pos1=startpos:Translate(maxview,heading1) -local pos2=startpos:Translate(maxview,heading2) -local array={} -table.insert(array,startpos:GetVec2()) -table.insert(array,pos1:GetVec2()) -table.insert(array,pos2:GetVec2()) -viewzone=ZONE_POLYGON:NewFromPointsArray(unitname,array) -else -local startp=unit:GetCoordinate() -local heading1=(vheading+90)%360 -local heading2=(vheading-90)%360 -self:T({heading1,heading2}) -local startpos=startp:Translate(minview,vheading) -local pos1=startpos:Translate(12.5,heading1) -local pos2=startpos:Translate(12.5,heading2) -local pos3=pos1:Translate(maxview,vheading) -local pos4=pos2:Translate(maxview,vheading) -local array={} -table.insert(array,pos1:GetVec2()) -table.insert(array,pos2:GetVec2()) -table.insert(array,pos4:GetVec2()) -table.insert(array,pos3:GetVec2()) -viewzone=ZONE_POLYGON:NewFromPointsArray(unitname,array) -end -end -return viewzone -end -function PLAYERRECCE:_GetKnownTargets(client) -self:T(self.lid.."_GetKnownTargets") -local finaltargets=SET_UNIT:New() -local targets=self.TargetCache:GetDataTable() -local playername=client:GetPlayerName() -for _,_target in pairs(targets)do -local targetdata=_target.PlayerRecceDetected -if targetdata.playername==playername then -finaltargets:Add(_target:GetName(),_target) -end -end -return finaltargets,finaltargets:CountAlive() -end -function PLAYERRECCE:_CleanupTargetCache() -self:T(self.lid.."_CleanupTargetCache") -local cleancache=FIFO:New() -self.TargetCache:ForEach( -function(unit) -local pull=false -if unit and unit:IsAlive()and unit:GetLife()>1 then -if unit.PlayerRecceDetected and unit.PlayerRecceDetected.timestamp then -local TNow=timer.getTime() -if TNow-unit.PlayerRecceDetected.timestamp>self.TForget then -pull=true -unit.PlayerRecceDetected=nil -end -else -pull=true -end -else -pull=true -end -if not pull then -cleancache:Push(unit,unit:GetName()) -end -end -) -self.TargetCache=nil -self.TargetCache=cleancache -return self -end -function PLAYERRECCE:_GetTargetSet(unit,camera,laser) -self:T(self.lid.."_GetTargetSet") -local finaltargets=SET_UNIT:New() -local finalcount=0 -local minview=0 -local typename=unit:GetTypeName() -local playername=unit:GetPlayerName() -local maxview=self.MaxViewDistance[typename]or 5000 -local heading,nod,maxview,angle=0,30,8000,10 -local camon=false -local name=unit:GetName() -if string.find(typename,"SA342")and camera then -heading,nod,maxview,camon=self:_GetGazelleVivianneSight(unit) -angle=10 -maxview=self.MaxViewDistance[typename]or 5000 -elseif string.find(typename,"Ka-50")and camera then -heading=unit:GetHeading() -nod,maxview,camon=10,1000,true -angle=10 -maxview=self.MaxViewDistance[typename]or 5000 -else -heading=unit:GetHeading() -nod,maxview,camon=10,1000,true -angle=45 -end -if laser then -if not self.LaserFOV[playername]then -minview=100 -maxview=2000 -self.LaserFOV[playername]={ -min=100, -max=2000, -} -else -minview=self.LaserFOV[playername].min -maxview=self.LaserFOV[playername].max -end -end -local zone=self:_GetViewZone(unit,heading,minview,maxview,angle,camon,laser) -if zone then -local redcoalition="red" -if self.Coalition==coalition.side.RED then -redcoalition="blue" -end -local startpos=unit:GetCoordinate() -local targetset=SET_UNIT:New():FilterCategories("ground"):FilterActive(true):FilterZones({zone}):FilterCoalitions(redcoalition):FilterOnce() -self:T("Prefilter Target Count = "..targetset:CountAlive()) -targetset:ForEach( -function(_unit) -local _unit=_unit -local _unitpos=_unit:GetCoordinate() -if startpos:IsLOS(_unitpos)and _unit:IsAlive()and _unit:GetLife()>1 then -self:T("Adding to final targets: ".._unit:GetName()) -finaltargets:Add(_unit:GetName(),_unit) -end -end -) -finalcount=finaltargets:CountAlive() -self:T(string.format("%s Unit: %s | Targets in view %s",self.lid,name,finalcount)) -end -return finaltargets,finalcount,zone -end -function PLAYERRECCE:_GetHVTTarget(targetset) -self:T(self.lid.."_GetHVTTarget") -local unitsbythreat={} -local minthreat=self.minthreatlevel or 0 -for _,_unit in pairs(targetset.Set)do -local unit=_unit -if unit and unit:IsAlive()and unit:GetLife()>1 then -local threat=unit:GetThreatLevel() -if threat>=minthreat then -if unit:HasAttribute("RADAR_BAND1_FOR_ARM")or unit:HasAttribute("RADAR_BAND2_FOR_ARM")or unit:HasAttribute("Optical Tracker")then -threat=11 -end -table.insert(unitsbythreat,{unit,threat}) -end -end -end -table.sort(unitsbythreat,function(a,b) -local aNum=a[2] -local bNum=b[2] -return aNum>bNum -end) -if unitsbythreat[1]and unitsbythreat[1][1]then -return unitsbythreat[1][1] -else -return nil -end -end -function PLAYERRECCE:_LaseTarget(client,targetset) -self:T(self.lid.."_LaseTarget") -local target=self:_GetHVTTarget(targetset) -local playername=client:GetPlayerName() -local laser=nil -if not self.LaserSpots[playername]then -laser=SPOT:New(client) -if not self.UnitLaserCodes[playername]then -self.UnitLaserCodes[playername]=1688 -end -laser.LaserCode=self.UnitLaserCodes[playername]or 1688 -self.LaserSpots[playername]=laser -else -laser=self.LaserSpots[playername] -end -if self.LaserTarget[playername]then -local target=self.LaserTarget[playername] -local oldtarget=target:GetObject() -self:T("Targetstate: "..target:GetState()) -self:T("Laser State: "..tostring(laser:IsLasing())) -if(not oldtarget)or targetset:IsNotInSet(oldtarget)or target:IsDead()or target:IsDestroyed()then -laser:LaseOff() -if target:IsDead()or target:IsDestroyed()or target:GetLife()<2 then -self:__Shack(-1,client,oldtarget) -else -self:__TargetLOSLost(-1,client,oldtarget) -end -self.LaserTarget[playername]=nil -oldtarget=nil -self.LaserSpots[playername]=nil -elseif oldtarget and laser and(not laser:IsLasing())then -self:T("Switching laser back on ..") -local lasercode=self.UnitLaserCodes[playername]or laser.LaserCode or 1688 -local lasingtime=self.lasingtime or 60 -laser:LaseOn(oldtarget,lasercode,lasingtime) -else -self:T("Target alive and laser is on!") -end -elseif(not laser:IsLasing())and target then -local relativecam=self.LaserRelativePos[client:GetTypeName()] -laser:SetRelativeStartPosition(relativecam) -local lasercode=self.UnitLaserCodes[playername]or laser.LaserCode or 1688 -local lasingtime=self.lasingtime or 60 -laser:LaseOn(target,lasercode,lasingtime) -self.LaserTarget[playername]=TARGET:New(target) -self:__TargetLasing(-1,client,target,lasercode,lasingtime) -end -return self -end -function PLAYERRECCE:_SetClientLaserCode(client,group,playername,code) -self:T(self.lid.."_SetClientLaserCode") -self.UnitLaserCodes[playername]=code or 1688 -if self.ClientMenus[playername]then -self.ClientMenus[playername]:Remove() -self.ClientMenus[playername]=nil -end -self:_BuildMenus() -return self -end -function PLAYERRECCE:_SwitchOnStation(client,group,playername) -self:T(self.lid.."_SwitchOnStation") -if not self.OnStation[playername]then -self.OnStation[playername]=true -self:__RecceOnStation(-1,client,playername) -else -self.OnStation[playername]=false -self:__RecceOffStation(-1,client,playername) -end -if self.ClientMenus[playername]then -self.ClientMenus[playername]:Remove() -self.ClientMenus[playername]=nil -end -self:_BuildMenus(client) -return self -end -function PLAYERRECCE:_SwitchSmoke(client,group,playername) -self:T(self.lid.."_SwitchLasing") -if not self.SmokeOwn[playername]then -self.SmokeOwn[playername]=true -MESSAGE:New("Smoke self is now ON",10,self.Name or"FACA"):ToClient(client) -else -self.SmokeOwn[playername]=false -MESSAGE:New("Smoke self is now OFF",10,self.Name or"FACA"):ToClient(client) -end -if self.ClientMenus[playername]then -self.ClientMenus[playername]:Remove() -self.ClientMenus[playername]=nil -end -self:_BuildMenus(client) -return self -end -function PLAYERRECCE:_SwitchLasing(client,group,playername) -self:T(self.lid.."_SwitchLasing") -if not self.AutoLase[playername]then -self.AutoLase[playername]=true -MESSAGE:New("Lasing is now ON",10,self.Name or"FACA"):ToClient(client) -else -self.AutoLase[playername]=false -if self.LaserSpots[playername]then -local laser=self.LaserSpots[playername] -if laser:IsLasing()then -laser:LaseOff() -end -self.LaserSpots[playername]=nil -end -MESSAGE:New("Lasing is now OFF",10,self.Name or"FACA"):ToClient(client) -end -if self.ClientMenus[playername]then -self.ClientMenus[playername]:Remove() -self.ClientMenus[playername]=nil -end -self:_BuildMenus(client) -return self -end -function PLAYERRECCE:_SwitchLasingDist(client,group,playername,mindist,maxdist) -self:T(self.lid.."_SwitchLasingDist") -local mind=mindist or 100 -local maxd=maxdist or 2000 -if not self.LaserFOV[playername]then -self.LaserFOV[playername]={ -min=mind, -max=maxd, -} -else -self.LaserFOV[playername].min=mind -self.LaserFOV[playername].max=maxd -end -MESSAGE:New(string.format("Laser distance set to %d-%dm!",mindist,maxdist),10,"FACA"):ToClient(client) -if self.ClientMenus[playername]then -self.ClientMenus[playername]:Remove() -self.ClientMenus[playername]=nil -end -self:_BuildMenus(client) -return self -end -function PLAYERRECCE:_WIP(client,group,playername) -self:T(self.lid.."_WIP") -return self -end -function PLAYERRECCE:_SmokeTargets(client,group,playername) -self:T(self.lid.."_SmokeTargets") -local cameraset=self:_GetTargetSet(client,true) -local visualset=self:_GetTargetSet(client,false) -if cameraset:CountAlive()>0 or visualset:CountAlive()>0 then -self:__TargetsSmoked(-1,client,playername,cameraset) -else -return self -end -local highsmoke=self.SmokeColor.highsmoke -local medsmoke=self.SmokeColor.medsmoke -local lowsmoke=self.SmokeColor.lowsmoke -local lasersmoke=self.SmokeColor.lasersmoke -local laser=self.LaserSpots[playername] -if laser and laser.Target and laser.Target:IsAlive()then -laser.Target:GetCoordinate():Smoke(lasersmoke) -end -local coord=visualset:GetCoordinate() -if coord and self.smokeaveragetargetpos then -coord:SetAtLandheight() -coord:Smoke(medsmoke) -else -for _,_unit in pairs(visualset.Set)do -local unit=_unit -if unit and unit:IsAlive()then -local coord=unit:GetCoordinate() -local threat=unit:GetThreatLevel() -if coord then -local color=lowsmoke -if threat>7 then -color=highsmoke -elseif threat>2 then -color=medsmoke -end -coord:Smoke(color) -end -end -end -end -if self.SmokeOwn[playername]then -local cc=client:GetVec2() -local lc=COORDINATE:NewFromVec2(cc,1) -local color=self.SmokeColor.ownsmoke -lc:Smoke(color) -end -return self -end -function PLAYERRECCE:_FlareTargets(client,group,playername) -self:T(self.lid.."_FlareTargets") -local cameraset=self:_GetTargetSet(client,true) -local visualset=self:_GetTargetSet(client,false) -cameraset:AddSet(visualset) -if cameraset:CountAlive()>0 then -self:__TargetsFlared(-1,client,playername,cameraset) -end -local highsmoke=self.FlareColor.highflare -local medsmoke=self.FlareColor.medflare -local lowsmoke=self.FlareColor.lowflare -local lasersmoke=self.FlareColor.laserflare -local laser=self.LaserSpots[playername] -if laser and laser.Target and laser.Target:IsAlive()then -laser.Target:GetCoordinate():Flare(lasersmoke) -if cameraset:IsInSet(laser.Target)then -cameraset:Remove(laser.Target:GetName(),true) -end -end -for _,_unit in pairs(cameraset.Set)do -local unit=_unit -if unit and unit:IsAlive()then -local coord=unit:GetCoordinate() -local threat=unit:GetThreatLevel() -if coord then -local color=lowsmoke -if threat>7 then -color=highsmoke -elseif threat>2 then -color=medsmoke -end -coord:Flare(color) -end -end -end -return self -end -function PLAYERRECCE:_IlluTargets(client,group,playername) -self:T(self.lid.."_IlluTargets") -local totalset,count=self:_GetKnownTargets(client) -if count>0 then -local coord=totalset:GetCoordinate() -coord.y=coord.y+200 -coord:IlluminationBomb(nil,1) -self:__Illumination(1,client,playername,totalset) -end -return self -end -function PLAYERRECCE:_UploadTargets(client,group,playername) -self:T(self.lid.."_UploadTargets") -local totalset,count=self:_GetKnownTargets(client) -if count>0 then -self.Controller:AddTarget(totalset) -self:__TargetReportSent(1,client,playername,totalset) -end -return self -end -function PLAYERRECCE:_ReportLaserTargets(client,group,playername) -self:T(self.lid.."_ReportLaserTargets") -local targetset,number=self:_GetTargetSet(client,true,true) -if number>0 and self.AutoLase[playername]then -local Settings=(client and _DATABASE:GetPlayerSettings(playername))or _SETTINGS -local target=self:_GetHVTTarget(targetset) -local ThreatLevel=target:GetThreatLevel()or 1 -local ThreatLevelText="high" -if ThreatLevel>3 and ThreatLevel<8 then -ThreatLevelText="medium" -elseif ThreatLevel<=3 then -ThreatLevelText="low" -end -local ThreatGraph="["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]: "..ThreatLevel -local report=REPORT:New("Lasing Report") -report:Add(string.rep("-",15)) -report:Add("Target type: "..target:GetTypeName()or"unknown") -report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") -if not self.ReferencePoint then -report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) -else -report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) -end -report:Add("Laser Code: "..self.UnitLaserCodes[playername]or 1688) -report:Add(string.rep("-",15)) -local text=report:Text() -self:__TargetReport(1,client,targetset,target,text) -else -local report=REPORT:New("Lasing Report") -report:Add(string.rep("-",15)) -report:Add("N O T A R G E T S") -report:Add(string.rep("-",15)) -local text=report:Text() -self:__TargetReport(1,client,nil,nil,text) -end -return self -end -function PLAYERRECCE:_ReportVisualTargets(client,group,playername) -self:T(self.lid.."_ReportVisualTargets") -local targetset,number=self:_GetKnownTargets(client) -if number>0 then -local Settings=(client and _DATABASE:GetPlayerSettings(playername))or _SETTINGS -local ThreatLevel=targetset:CalculateThreatLevelA2G() -local ThreatLevelText="high" -if ThreatLevel>3 and ThreatLevel<8 then -ThreatLevelText="medium" -elseif ThreatLevel<=3 then -ThreatLevelText="low" -end -local ThreatGraph="["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]: "..ThreatLevel -local report=REPORT:New("Target Report") -report:Add(string.rep("-",15)) -report:Add("Target count: "..number) -report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") -if not self.ReferencePoint then -report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) -else -report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) -end -report:Add(string.rep("-",15)) -local text=report:Text() -self:__TargetReport(1,client,targetset,nil,text) -else -local report=REPORT:New("Target Report") -report:Add(string.rep("-",15)) -report:Add("N O T A R G E T S") -report:Add(string.rep("-",15)) -local text=report:Text() -self:__TargetReport(1,client,nil,nil,text) -end -return self -end -function PLAYERRECCE:_BuildMenus(Client) -self:T(self.lid.."_BuildMenus") -local clients=self.PlayerSet -local clientset=clients:GetSetObjects() -if Client then clientset={Client}end -for _,_client in pairs(clientset)do -local client=_client -if client and client:IsAlive()then -local playername=client:GetPlayerName() -if not self.UnitLaserCodes[playername]then -self:_SetClientLaserCode(nil,nil,playername,1688) -end -if self.SmokeOwn[playername]==nil then -self.SmokeOwn[playername]=self.smokeownposition -end -local group=client:GetGroup() -if not self.ClientMenus[playername]then -local canlase=self.CanLase[client:GetTypeName()] -self.ClientMenus[playername]=MENU_GROUP:New(group,self.MenuName or self.Name or"RECCE") -local txtonstation=self.OnStation[playername]and"ON"or"OFF" -local text=string.format("Switch On-Station (%s)",txtonstation) -local onstationmenu=MENU_GROUP_COMMAND:New(group,text,self.ClientMenus[playername],self._SwitchOnStation,self,client,group,playername) -if self.OnStation[playername]then -local smoketopmenu=MENU_GROUP:New(group,"Visual Markers",self.ClientMenus[playername]) -local smokemenu=MENU_GROUP_COMMAND:New(group,"Smoke Targets",smoketopmenu,self._SmokeTargets,self,client,group,playername) -local flaremenu=MENU_GROUP_COMMAND:New(group,"Flare Targets",smoketopmenu,self._FlareTargets,self,client,group,playername) -local illumenu=MENU_GROUP_COMMAND:New(group,"Illuminate Area",smoketopmenu,self._IlluTargets,self,client,group,playername) -local ownsm=self.SmokeOwn[playername]and"ON"or"OFF" -local owntxt=string.format("Switch smoke self (%s)",ownsm) -local ownsmoke=MENU_GROUP_COMMAND:New(group,owntxt,smoketopmenu,self._SwitchSmoke,self,client,group,playername) -if canlase then -local txtonstation=self.AutoLase[playername]and"ON"or"OFF" -local text=string.format("Switch Lasing (%s)",txtonstation) -local lasemenu=MENU_GROUP_COMMAND:New(group,text,self.ClientMenus[playername],self._SwitchLasing,self,client,group,playername) -local lasedist=MENU_GROUP:New(group,"Set Laser Distance",self.ClientMenus[playername]) -local mindist=100 -local maxdist=2000 -if self.LaserFOV[playername]and self.LaserFOV[playername].max then -maxdist=self.LaserFOV[playername].max -end -local laselist={} -for i=2,8 do -local dist1=(i*1000)-1000 -local dist2=i*1000 -dist1=dist1==1000 and 100 or dist1 -local text=string.format("%d-%dm",dist1,dist2) -if dist2==maxdist then -text=text.." (*)" -end -laselist[i]=MENU_GROUP_COMMAND:New(group,text,lasedist,self._SwitchLasingDist,self,client,group,playername,dist1,dist2) -end -end -local targetmenu=MENU_GROUP:New(group,"Target Report",self.ClientMenus[playername]) -if canlase then -local reportL=MENU_GROUP_COMMAND:New(group,"Laser Target",targetmenu,self._ReportLaserTargets,self,client,group,playername) -end -local reportV=MENU_GROUP_COMMAND:New(group,"Visual Targets",targetmenu,self._ReportVisualTargets,self,client,group,playername) -if self.UseController then -local text=string.format("Target Upload to %s",self.Controller.MenuName or self.Controller.Name) -local upload=MENU_GROUP_COMMAND:New(group,text,targetmenu,self._UploadTargets,self,client,group,playername) -end -if canlase then -local lasecodemenu=MENU_GROUP:New(group,"Set Laser Code",self.ClientMenus[playername]) -local codemenu={} -for _,_code in pairs(self.LaserCodes)do -if _code==self.UnitLaserCodes[playername]then -_code=tostring(_code).."(*)" -end -codemenu[playername.._code]=MENU_GROUP_COMMAND:New(group,tostring(_code),lasecodemenu,self._SetClientLaserCode,self,client,group,playername,_code) -end -end -end -end -end -end -return self -end -function PLAYERRECCE:_CheckNewTargets(targetset,client,playername) -self:T(self.lid.."_CheckNewTargets") -local tempset=SET_UNIT:New() -targetset:ForEach( -function(unit) -if unit and unit:IsAlive()then -self:T("Report unit: "..unit:GetName()) -if not unit.PlayerRecceDetected then -self:T("New unit: "..unit:GetName()) -unit.PlayerRecceDetected={ -detected=true, -recce=client, -playername=playername, -timestamp=timer.getTime() -} -tempset:Add(unit:GetName(),unit) -if not self.TargetCache:HasUniqueID(unit:GetName())then -self.TargetCache:Push(unit,unit:GetName()) -end -end -if unit.PlayerRecceDetected and unit.PlayerRecceDetected.timestamp then -local TNow=timer.getTime() -if TNow-unit.PlayerRecceDetected.timestamp>self.TForget then -unit.PlayerRecceDetected={ -detected=true, -recce=client, -playername=playername, -timestamp=timer.getTime() -} -if not self.TargetCache:HasUniqueID(unit:GetName())then -self.TargetCache:Push(unit,unit:GetName()) -end -tempset:Add(unit:GetName(),unit) -end -end -end -end -) -local targetsbyclock={} -for i=1,12 do -targetsbyclock[i]={} -end -tempset:ForEach( -function(object) -local obj=object -local clock=self:_GetClockDirection(client,obj) -table.insert(targetsbyclock[clock],obj) -end -) -self:T("Known target Count: "..self.TargetCache:Count()) -if tempset:CountAlive()>0 then -self:TargetDetected(targetsbyclock,client,playername) -end -return self -end -function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey) -self:T(self.lid.."SetSRS") -self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone" -self.Gender=Gender or MSRS.gender or"male" -self.Culture=Culture or MSRS.culture or"en-US" -self.Port=Port or MSRS.port or 5002 -self.Voice=Voice or MSRS.voice -self.PathToGoogleKey=PathToGoogleKey -self.Volume=Volume or 1.0 -self.UseSRS=true -self.Frequency=Frequency or{127,251} -self.BCFrequency=self.Frequency -self.Modulation=Modulation or{radio.modulation.FM,radio.modulation.AM} -self.BCModulation=self.Modulation -self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation) -self.SRS:SetCoalition(self.Coalition) -self.SRS:SetLabel(self.MenuName or self.Name) -self.SRS:SetGender(self.Gender) -self.SRS:SetCulture(self.Culture) -self.SRS:SetPort(self.Port) -self.SRS:SetVolume(self.Volume) -if self.PathToGoogleKey then -self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey) -self.SRS:SetProvider(MSRS.Provider.GOOGLE) -end -if(not PathToGoogleKey)and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then -self.PathToGoogleKey=MSRS.poptions.gcloud.credentials -self.Voice=Voice or MSRS.poptions.gcloud.voice -end -self.SRS:SetVoice(self.Voice) -self.SRSQueue=MSRSQUEUE:New(self.MenuName or self.Name) -self.SRSQueue:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers) -return self -end -function PLAYERRECCE:SetTransmitOnlyWithPlayers(Switch) -self.TransmitOnlyWithPlayers=Switch -if self.SRSQueue then -self.SRSQueue:SetTransmitOnlyWithPlayers(Switch) -end -return self -end -function PLAYERRECCE:SetMenuName(Name) -self:T(self.lid.."SetMenuName: "..Name) -self.MenuName=Name -return self -end -function PLAYERRECCE:EnableSmokeOwnPosition() -self:T(self.lid.."EnableSmokeOwnPosition") -self.smokeownposition=true -return self -end -function PLAYERRECCE:DisableSmokeOwnPosition() -self:T(self.lid.."DisableSmokeOwnPosition") -self.smokeownposition=false -return self -end -function PLAYERRECCE:EnableSmokeAverageTargetPosition() -self:T(self.lid.."ENableSmokeOwnPosition") -self.smokeaveragetargetpos=true -return self -end -function PLAYERRECCE:DisableSmokeAverageTargetPosition() -self:T(self.lid.."DisableSmokeAverageTargetPosition") -self.smokeaveragetargetpos=false -return self -end -function PLAYERRECCE:_GetTextForSpeech(text) -text=string.gsub(text,"%d","%1 ") -text=string.gsub(text,"^%s*","") -text=string.gsub(text,"%s*$","") -text=string.gsub(text,"0","zero") -text=string.gsub(text,"9","niner") -text=string.gsub(text," "," ") -return text -end -function PLAYERRECCE:onafterStatus(From,Event,To) -self:T({From,Event,To}) -if not self.timestamp then -self.timestamp=timer.getTime() -else -local tNow=timer.getTime() -if tNow-self.timestamp>=60 then -self:_CleanupTargetCache() -self.timestamp=timer.getTime() -end -end -self:_BuildMenus() -self.PlayerSet:ForEachClient( -function(Client) -local client=Client -local playername=client:GetPlayerName() -local cameraison=self:_CameraOn(client,playername) -if client and client:IsAlive()and self.OnStation[playername]then -local targetset,targetcount,tzone=nil,0,nil -local laserset,lzone=nil,nil -local vistargetset,vistargetcount,viszone=nil,0,nil -if cameraison then -targetset,targetcount,tzone=self:_GetTargetSet(client,true) -if targetset then -if self.ViewZone[playername]then -self.ViewZone[playername]:UndrawZone() -end -if self.debug and tzone then -self.ViewZone[playername]=tzone:DrawZone(self.Coalition,{0,0,1},nil,nil,nil,1) -end -end -self:T({targetcount=targetcount}) -end -if self.AutoLase[playername]and cameraison then -laserset,targetcount,lzone=self:_GetTargetSet(client,true,true) -if targetcount>0 or self.LaserTarget[playername]then -if self.CanLase[client:GetTypeName()]then -self:_LaseTarget(client,laserset) -end -end -if lzone then -if self.ViewZoneLaser[playername]then -self.ViewZoneLaser[playername]:UndrawZone() -end -if self.debug and tzone then -self.ViewZoneLaser[playername]=lzone:DrawZone(self.Coalition,{0,1,0},nil,nil,nil,1) -end -end -self:T({lasercount=targetcount}) -end -vistargetset,vistargetcount,viszone=self:_GetTargetSet(client,false) -if vistargetset then -if self.ViewZoneVisual[playername]then -self.ViewZoneVisual[playername]:UndrawZone() -end -if self.debug and viszone then -self.ViewZoneVisual[playername]=viszone:DrawZone(self.Coalition,{1,0,0},nil,nil,nil,3) -end -end -self:T({visualtargetcount=vistargetcount}) -if targetset then -vistargetset:AddSet(targetset) -end -if laserset then -vistargetset:AddSet(laserset) -end -if not cameraison and self.debug then -if self.ViewZoneLaser[playername]then -self.ViewZoneLaser[playername]:UndrawZone() -end -if self.ViewZone[playername]then -self.ViewZone[playername]:UndrawZone() -end -end -self:_CheckNewTargets(vistargetset,client,playername) -end -end -) -self:__Status(-10) -return self -end -function PLAYERRECCE:onafterRecceOnStation(From,Event,To,Client,Playername) -self:T({From,Event,To}) -local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) -local coord=Client:GetCoordinate() -local coordtext=coord:ToStringBULLS(self.Coalition) -if self.ReferencePoint then -local Settings=Client and _DATABASE:GetPlayerSettings(Playername)or _SETTINGS -coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) -end -local text1="Party time!" -local text2=string.format("All stations, FACA %s on station\nat %s!",callsign,coordtext) -local text2tts=string.format("All stations, FACA %s on station at %s!",callsign,coordtext) -text2tts=self:_GetTextForSpeech(text2tts) -if self.debug then -self:T(text2.."\n"..text2tts) -end -if self.UseSRS then -local grp=Client:GetGroup() -local coord=grp:GetCoordinate() -if coord then -self.SRS:SetCoordinate(coord) -end -self.SRSQueue:NewTransmission(text1,nil,self.SRS,nil,2) -self.SRSQueue:NewTransmission(text2tts,nil,self.SRS,nil,3) -MESSAGE:New(text2,10,self.Name or"FACA"):ToCoalition(self.Coalition) -else -MESSAGE:New(text1,10,self.Name or"FACA"):ToClient(Client) -MESSAGE:New(text2,10,self.Name or"FACA"):ToCoalition(self.Coalition) -end -return self -end -function PLAYERRECCE:onafterRecceOffStation(From,Event,To,Client,Playername) -self:T({From,Event,To}) -local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) -local coord=Client:GetCoordinate() -local coordtext=coord:ToStringBULLS(self.Coalition) -if self.ReferencePoint then -local Settings=Client and _DATABASE:GetPlayerSettings(Playername)or _SETTINGS -coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) -end -local text=string.format("All stations, FACA %s leaving station\nat %s, good bye!",callsign,coordtext) -local texttts=string.format("All stations, FACA %s leaving station at %s, good bye!",callsign,coordtext) -texttts=self:_GetTextForSpeech(texttts) -if self.debug then -self:T(text.."\n"..texttts) -end -local text1="Going home!" -if self.UseSRS then -local grp=Client:GetGroup() -local coord=grp:GetCoordinate() -if coord then -self.SRS:SetCoordinate(coord) -end -self.SRSQueue:NewTransmission(text1,nil,self.SRS,nil,2) -self.SRSQueue:NewTransmission(texttts,nil,self.SRS,nil,3) -MESSAGE:New(text,10,self.Name or"FACA"):ToCoalition(self.Coalition) -else -MESSAGE:New(text,10,self.Name or"FACA"):ToCoalition(self.Coalition) -end -return self -end -function PLAYERRECCE:onafterTargetDetected(From,Event,To,Targetsbyclock,Client,Playername) -self:T({From,Event,To}) -local dunits="meters" -local Settings=Client and _DATABASE:GetPlayerSettings(Playername)or _SETTINGS -local clientcoord=Client:GetCoordinate() -for i=1,12 do -local targets=Targetsbyclock[i] -local targetno=#targets -if targetno==1 then -local targetdistance=clientcoord:Get2DDistance(targets[1]:GetCoordinate())or 100 -local Threatlvl=targets[1]:GetThreatLevel() -local ThreatTxt="Low" -if Threatlvl>=7 then -ThreatTxt="Medium" -elseif Threatlvl>=3 then -ThreatTxt="High" -end -if Settings:IsMetric()then -targetdistance=UTILS.Round(targetdistance,-2) -if targetdistance>=1000 then -targetdistance=UTILS.Round(targetdistance/1000,0) -dunits="kilometer" -end -else -if UTILS.MetersToNM(targetdistance)>=1 then -targetdistance=UTILS.Round(UTILS.MetersToNM(targetdistance),0) -dunits="miles" -else -targetdistance=UTILS.Round(UTILS.MetersToFeet(targetdistance),-2) -dunits="feet" -end -end -local text=string.format("Target! %s! %s o\'clock, %d %s!",ThreatTxt,i,targetdistance,dunits) -local ttstext=string.format("Target! %s! %s oh clock, %d %s!",ThreatTxt,i,targetdistance,dunits) -if self.UseSRS then -local grp=Client:GetGroup() -if clientcoord then -self.SRS:SetCoordinate(clientcoord) -end -self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) -else -MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) -end -elseif targetno>1 then -local function GetNearest(TTable) -local distance=10000000 -for _,_unit in pairs(TTable)do -local dist=clientcoord:Get2DDistance(_unit:GetCoordinate())or 100 -if dist=1000 then -targetdistance=UTILS.Round(targetdistance/1000,0) -dunits="kilometer" -end -else -if UTILS.MetersToNM(targetdistance)>=1 then -targetdistance=UTILS.Round(UTILS.MetersToNM(targetdistance),0) -dunits="miles" -else -targetdistance=UTILS.Round(UTILS.MetersToFeet(targetdistance),-2) -dunits="feet" -end -end -local text=string.format(" %d targets! %s o\'clock, %d %s!",targetno,i,targetdistance,dunits) -local ttstext=string.format("%d targets! %s oh clock, %d %s!",targetno,i,targetdistance,dunits) -if self.UseSRS then -local grp=Client:GetGroup() -local coord=grp:GetCoordinate() -if coord then -self.SRS:SetCoordinate(coord) -end -self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) -else -MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) -end -end -end -return self -end -function PLAYERRECCE:onafterIllumination(From,Event,To,Client,Playername,TargetSet) -self:T({From,Event,To}) -local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) -local coord=Client:GetCoordinate() -local coordtext=coord:ToStringBULLS(self.Coalition) -if self.AttackSet then -for _,_client in pairs(self.AttackSet.Set)do -local client=_client -if client and client:IsAlive()then -local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS -local coordtext=coord:ToStringA2G(client,Settings) -if self.ReferencePoint then -coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) -end -local text=string.format("All stations, %s fired illumination\nat %s!",callsign,coordtext) -MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client) -end -end -end -local text="Sunshine!" -local ttstext="Sunshine!" -if self.UseSRS then -local grp=Client:GetGroup() -local coord=grp:GetCoordinate() -if coord then -self.SRS:SetCoordinate(coord) -end -self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) -else -MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) -end -return self -end -function PLAYERRECCE:onafterTargetsSmoked(From,Event,To,Client,Playername,TargetSet) -self:T({From,Event,To}) -local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) -local coord=Client:GetCoordinate() -local coordtext=coord:ToStringBULLS(self.Coalition) -if self.AttackSet then -for _,_client in pairs(self.AttackSet.Set)do -local client=_client -if client and client:IsAlive()then -local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS -local coordtext=coord:ToStringA2G(client,Settings) -if self.ReferencePoint then -coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) -end -local text=string.format("All stations, %s smoked targets\nat %s!",callsign,coordtext) -MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client) -end -end -end -local text="Smoke on!" -local ttstext="Smoke and Mirrors!" -if self.UseSRS then -local grp=Client:GetGroup() -local coord=grp:GetCoordinate() -if coord then -self.SRS:SetCoordinate(coord) -end -self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) -else -MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) -end -return self -end -function PLAYERRECCE:onafterTargetsFlared(From,Event,To,Client,Playername,TargetSet) -self:T({From,Event,To}) -local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) -local coord=Client:GetCoordinate() -local coordtext=coord:ToStringBULLS(self.Coalition) -if self.AttackSet then -for _,_client in pairs(self.AttackSet.Set)do -local client=_client -if client and client:IsAlive()then -local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS -if self.ReferencePoint then -coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) -end -local coordtext=coord:ToStringA2G(client,Settings) -local text=string.format("All stations, %s flared targets\nat %s!",callsign,coordtext) -MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client) -end -end -end -local text="Fireworks!" -local ttstext="Fire works!" -if self.UseSRS then -local grp=Client:GetGroup() -local coord=grp:GetCoordinate() -if coord then -self.SRS:SetCoordinate(coord) -end -self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) -else -MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) -end -return self -end -function PLAYERRECCE:onafterTargetLasing(From,Event,To,Client,Target,Lasercode,Lasingtime) -self:T({From,Event,To}) -local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) -local Settings=(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS -local coord=Client:GetCoordinate() -local coordtext=coord:ToStringBULLS(self.Coalition,Settings) -if self.ReferencePoint then -coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) -end -local targettype=Target:GetTypeName() -if self.AttackSet then -for _,_client in pairs(self.AttackSet.Set)do -local client=_client -if client and client:IsAlive()then -local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS -if self.ReferencePoint then -coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) -end -local coordtext=coord:ToStringA2G(client,Settings) -local text=string.format("All stations, %s lasing %s\nat %s!\nCode %d, Duration %d plus seconds!",callsign,targettype,coordtext,Lasercode,Lasingtime) -MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client) -end -end -end -local text="Lasing!" -local ttstext="Laser on!" -if self.UseSRS then -local grp=Client:GetGroup() -local coord=grp:GetCoordinate() -if coord then -self.SRS:SetCoordinate(coord) -end -self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) -else -MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) -end -return self -end -function PLAYERRECCE:onafterShack(From,Event,To,Client,Target) -self:T({From,Event,To}) -local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) -local Settings=(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS -local coord=Client:GetCoordinate() -local coordtext=coord:ToStringBULLS(self.Coalition,Settings) -if self.ReferencePoint then -coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) -end -local targettype="target" -if self.AttackSet then -for _,_client in pairs(self.AttackSet.Set)do -local client=_client -if client and client:IsAlive()then -local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS -if self.ReferencePoint then -coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) -end -local coordtext=coord:ToStringA2G(client,Settings) -local text=string.format("All stations, %s good hit on %s\nat %s!",callsign,targettype,coordtext) -MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client) -end -end -end -local text="Shack!" -local ttstext="Shack!" -if self.UseSRS then -local grp=Client:GetGroup() -local coord=grp:GetCoordinate() -if coord then -self.SRS:SetCoordinate(coord) -end -self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1) -else -MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) -end -return self -end -function PLAYERRECCE:onafterTargetLOSLost(From,Event,To,Client,Target) -self:T({From,Event,To}) -local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) -local Settings=(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS -local coord=Client:GetCoordinate() -local coordtext=coord:ToStringBULLS(self.Coalition,Settings) -if self.ReferencePoint then -coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) -end -local targettype="target" -if self.AttackSet then -for _,_client in pairs(self.AttackSet.Set)do -local client=_client -if client and client:IsAlive()then -local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS -if self.ReferencePoint then -coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) -end -local coordtext=coord:ToStringA2G(client,Settings) -local text=string.format("All stations, %s lost sight of %s\nat %s!",callsign,targettype,coordtext) -MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client) -end -end -end -local text="Lost LOS!" -local ttstext="Lost L O S!" -if self.UseSRS then -local grp=Client:GetGroup() -local coord=grp:GetCoordinate() -if coord then -self.SRS:SetCoordinate(coord) -end -self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) -else -MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) -end -return self -end -function PLAYERRECCE:onafterTargetReport(From,Event,To,Client,TargetSet,Target,Text) -self:T({From,Event,To}) -MESSAGE:New(Text,45,self.Name or"FACA"):ToClient(Client) -if self.AttackSet then -for _,_client in pairs(self.AttackSet.Set)do -local client=_client -if client and client:IsAlive()and client~=Client then -MESSAGE:New(Text,45,self.Name or"FACA"):ToClient(client) -end -end -end -return self -end -function PLAYERRECCE:onafterTargetReportSent(From,Event,To,Client,Playername,TargetSet) -self:T({From,Event,To}) -local text="Upload completed!" -if self.UseSRS then -local grp=Client:GetGroup() -local coord=grp:GetCoordinate() -if coord then -self.SRS:SetCoordinate(coord) -end -self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,1,{grp},text,10) -else -MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) -end -return self -end -function PLAYERRECCE:onafterStop(From,Event,To) -self:I({From,Event,To}) -self:UnHandleEvent(EVENTS.PlayerLeaveUnit) -self:UnHandleEvent(EVENTS.Ejection) -self:UnHandleEvent(EVENTS.Crash) -self:UnHandleEvent(EVENTS.PilotDead) -self:UnHandleEvent(EVENTS.PlayerEnterAircraft) -return self -end -SQUADRON={ -ClassName="SQUADRON", -verbose=0, -modex=nil, -modexcounter=0, -callsignName=nil, -callsigncounter=11, -tankerSystem=nil, -refuelSystem=nil, -} -SQUADRON.version="0.8.1" -function SQUADRON:New(TemplateGroupName,Ngroups,SquadronName) -local self=BASE:Inherit(self,COHORT:New(TemplateGroupName,Ngroups,SquadronName)) -self:AddMissionCapability(AUFTRAG.Type.ORBIT) -self.isAir=true -self.refuelSystem=select(2,self.templategroup:GetUnit(1):IsRefuelable()) -self.tankerSystem=select(2,self.templategroup:GetUnit(1):IsTanker()) -return self -end -function SQUADRON:SetGrouping(nunits) -self.ngrouping=nunits or 2 -if self.ngrouping<1 then self.ngrouping=1 end -if self.ngrouping>4 then self.ngrouping=4 end -return self -end -function SQUADRON:SetParkingIDs(ParkingIDs) -if type(ParkingIDs)~="table"then -ParkingIDs={ParkingIDs} -end -self.parkingIDs=ParkingIDs -return self -end -function SQUADRON:SetTakeoffType(TakeoffType) -TakeoffType=TakeoffType or"Cold" -if TakeoffType:lower()=="hot"then -self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot -elseif TakeoffType:lower()=="cold"then -self.takeoffType=COORDINATE.WaypointType.TakeOffParking -elseif TakeoffType:lower()=="air"then -self.takeoffType=COORDINATE.WaypointType.TurningPoint -else -self.takeoffType=COORDINATE.WaypointType.TakeOffParking -end -return self -end -function SQUADRON:SetTakeoffCold() -self:SetTakeoffType("Cold") -return self -end -function SQUADRON:SetTakeoffHot() -self:SetTakeoffType("Hot") -return self -end -function SQUADRON:SetTakeoffAir() -self:SetTakeoffType("Air") -return self -end -function SQUADRON:SetDespawnAfterLanding(Switch) -if Switch then -self.despawnAfterLanding=Switch -else -self.despawnAfterLanding=true -end -return self -end -function SQUADRON:SetDespawnAfterHolding(Switch) -if Switch then -self.despawnAfterHolding=Switch -else -self.despawnAfterHolding=true -end -return self -end -function SQUADRON:SetFuelLowThreshold(LowFuel) -self.fuellow=LowFuel or 25 -return self -end -function SQUADRON:SetFuelLowRefuel(switch) -if switch==false then -self.fuellowRefuel=false -else -self.fuellowRefuel=true -end -return self -end -function SQUADRON:SetAirwing(Airwing) -self.legion=Airwing -return self -end -function SQUADRON:GetAirwing(Airwing) -return self.legion -end -function SQUADRON:onafterStart(From,Event,To) -local text=string.format("Starting SQUADRON",self.name) -self:T(self.lid..text) -self:__Status(-1) -end -function SQUADRON:onafterStatus(From,Event,To) -if self.verbose>=1 then -local fsmstate=self:GetState() -local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName)or"N/A" -local modex=self.modex and self.modex or-1 -local skill=self.skill and tostring(self.skill)or"N/A" -local NassetsTot=#self.assets -local NassetsInS=self:CountAssets(true) -local NassetsQP=0;local NassetsP=0;local NassetsQ=0 -if self.legion then -NassetsQP,NassetsP,NassetsQ=self.legion:CountAssetsOnMission(nil,self) -end -local text=string.format("%s [Type=%s, Call=%s, Modex=%d, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]", -fsmstate,self.aircrafttype,callsign,modex,skill,NassetsTot,NassetsInS,NassetsQP,NassetsP,NassetsQ) -self:I(self.lid..text) -self:_CheckAssetStatus() -end -if not self:IsStopped()then -self:__Status(-60) -end -end -TARGET={ -ClassName="TARGET", -verbose=0, -lid=nil, -targets={}, -targetcounter=0, -life=0, -life0=0, -N0=0, -Ntargets0=0, -Ndestroyed=0, -Ndead=0, -elements={}, -casualties={}, -threatlevel0=0, -conditionStart={}, -TStatus=30, -} -TARGET.ObjectType={ -GROUP="Group", -UNIT="Unit", -STATIC="Static", -SCENERY="Scenery", -COORDINATE="Coordinate", -AIRBASE="Airbase", -ZONE="Zone", -OPSZONE="OpsZone" -} -TARGET.Category={ -AIRCRAFT="Aircraft", -GROUND="Ground", -NAVAL="Naval", -AIRBASE="Airbase", -COORDINATE="Coordinate", -ZONE="Zone", -} -TARGET.ObjectStatus={ -ALIVE="Alive", -DEAD="Dead", -DAMAGED="Damaged", -} -_TARGETID=0 -TARGET.version="0.6.0" -function TARGET:New(TargetObject) -local self=BASE:Inherit(self,FSM:New()) -_TARGETID=_TARGETID+1 -self.uid=_TARGETID -if TargetObject then -self:AddObject(TargetObject) -end -self:SetPriority() -self:SetImportance() -self.TStatus=30 -self.lid=string.format("TARGET #%03d | ",_TARGETID) -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Alive") -self:AddTransition("*","Status","*") -self:AddTransition("*","Stop","Stopped") -self:AddTransition("*","ObjectDamaged","*") -self:AddTransition("*","ObjectDestroyed","*") -self:AddTransition("*","ObjectDead","*") -self:AddTransition("*","Damaged","Damaged") -self:AddTransition("*","Destroyed","Dead") -self:AddTransition("*","Dead","Dead") -self:__Start(-1) -return self -end -function TARGET:AddObject(Object) -if Object:IsInstanceOf("SET_GROUP")or -Object:IsInstanceOf("SET_UNIT")or -Object:IsInstanceOf("SET_STATIC")or -Object:IsInstanceOf("SET_SCENERY")or -Object:IsInstanceOf("SET_OPSGROUP")or -Object:IsInstanceOf("SET_OPSZONE")then -local set=Object -for _,object in pairs(set.Set)do -self:AddObject(object) -end -elseif Object:IsInstanceOf("SET_ZONE")then -local set=Object -set:SortByName() -for index,ZoneName in pairs(set.Index)do -local zone=set.Set[ZoneName] -self:_AddObject(zone) -end -else -if Object:IsInstanceOf("OPSGROUP")then -self:_AddObject(Object:GetGroup()) -else -self:_AddObject(Object) -end -end -return self -end -function TARGET:SetPriority(Priority) -self.prio=Priority or 50 -return self -end -function TARGET:SetImportance(Importance) -self.importance=Importance -return self -end -function TARGET:AddConditionStart(ConditionFunction,...) -local condition={} -condition.func=ConditionFunction -condition.arg={} -if arg then -condition.arg=arg -end -table.insert(self.conditionStart,condition) -return self -end -function TARGET:AddConditionStop(ConditionFunction,...) -local condition={} -condition.func=ConditionFunction -condition.arg={} -if arg then -condition.arg=arg -end -table.insert(self.conditionStop,condition) -return self -end -function TARGET:EvalConditionsAll(Conditions) -for _,_condition in pairs(Conditions or{})do -local condition=_condition -local istrue=condition.func(unpack(condition.arg)) -if not istrue then -return false -end -end -return true -end -function TARGET:EvalConditionsAny(Conditions) -for _,_condition in pairs(Conditions or{})do -local condition=_condition -local istrue=condition.func(unpack(condition.arg)) -if istrue then -return true -end -end -return false -end -function TARGET:AddResource(MissionType,Nmin,Nmax,Attributes,Properties) -if Attributes and type(Attributes)~="table"then -Attributes={Attributes} -end -if Properties and type(Properties)~="table"then -Properties={Properties} -end -local resource={} -resource.MissionType=MissionType -resource.Nmin=Nmin or 1 -resource.Nmax=Nmax or 1 -resource.Attributes=Attributes or{} -resource.Properties=Properties or{} -self.resources=self.resources or{} -table.insert(self.resources,resource) -if self.verbose>10 then -local text="Resource:" -for _,_r in pairs(self.resources)do -local r=_r -text=text..string.format("\nmission=%s, Nmin=%d, Nmax=%d, attribute=%s, properties=%s",r.MissionType,r.Nmin,r.Nmax,tostring(r.Attributes[1]),tostring(r.Properties[1])) -end -self:I(self.lid..text) -end -return resource -end -function TARGET:IsAlive() -for _,_target in pairs(self.targets)do -local target=_target -if target.Status~=TARGET.ObjectStatus.DEAD then -return true -end -end -return false -end -function TARGET:IsDestroyed() -return self.isDestroyed -end -function TARGET:IsDead() -local is=self:Is("Dead") -return is -end -function TARGET:onafterStart(From,Event,To) -self:T({From,Event,To}) -local text=string.format("Starting Target") -self:T(self.lid..text) -self:HandleEvent(EVENTS.Dead,self.OnEventUnitDeadOrLost) -self:HandleEvent(EVENTS.UnitLost,self.OnEventUnitDeadOrLost) -self:HandleEvent(EVENTS.RemoveUnit,self.OnEventUnitDeadOrLost) -self:__Status(-1) -return self -end -function TARGET:onafterStatus(From,Event,To) -self:T({From,Event,To}) -local fsmstate=self:GetState() -local damaged=false -for i,_target in pairs(self.targets)do -local target=_target -local life=target.Life -target.Life=self:GetTargetLife(target) -if target.Life>target.Life0 then -local delta=2*(target.Life-target.Life0) -target.Life0=target.Life0+delta -life=target.Life0 -self.life0=self.life0+delta -end -if target.Life object dead now for target object %s!",tostring(target.Name))) -self:ObjectDead(target) -damaged=true -end -end -if damaged then -self:Damaged() -end -if self.verbose>=1 then -local text=string.format("%s: Targets=%d/%d Life=%.1f/%.1f Damage=%.1f",fsmstate,self:CountTargets(),self.N0,self:GetLife(),self:GetLife0(),self:GetDamage()) -if self:CountTargets()==0 or self:GetDamage()>=100 then -text=text.." Dead!" -elseif damaged then -text=text.." Damaged!" -end -self:I(self.lid..text) -end -if self.verbose>=2 then -local text="Target:" -for i,_target in pairs(self.targets)do -local target=_target -local damage=(1-target.Life/target.Life0)*100 -text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f",i,target.Type,target.Name,target.Status,target.Life,target.Life0,damage) -end -self:I(self.lid..text) -end -if self:CountTargets()==0 or self:GetDamage()>=100 then -self:Dead() -end -if self:IsAlive()then -self:__Status(-self.TStatus) -end -return self -end -function TARGET:onafterObjectDamaged(From,Event,To,Target) -self:T({From,Event,To}) -self:T(self.lid..string.format("Object %s damaged",Target.Name)) -return self -end -function TARGET:onafterObjectDestroyed(From,Event,To,Target) -self:T({From,Event,To}) -self:T(self.lid..string.format("Object %s destroyed",Target.Name)) -self.Ndestroyed=self.Ndestroyed+1 -self:ObjectDead(Target) -return self -end -function TARGET:onafterObjectDead(From,Event,To,Target) -self:T({From,Event,To}) -self:T(self.lid..string.format("Object %s dead",Target.Name)) -Target.Status=TARGET.ObjectStatus.DEAD -self.Ndead=self.Ndead+1 -local dead=true -for _,_target in pairs(self.targets)do -local target=_target -if target.Status==TARGET.ObjectStatus.ALIVE then -dead=false -end -end -if dead then -if self.Ndestroyed==self.Ntargets0 then -self.isDestroyed=true -self:Destroyed() -else -self:Dead() -end -else -self:Damaged() -end -return self -end -function TARGET:onafterDamaged(From,Event,To) -self:T({From,Event,To}) -self:T(self.lid..string.format("TARGET damaged")) -return self -end -function TARGET:onafterDestroyed(From,Event,To) -self:T({From,Event,To}) -self:T(self.lid..string.format("TARGET destroyed")) -self:Dead() -return self -end -function TARGET:onafterDead(From,Event,To) -self:T({From,Event,To}) -self:T(self.lid..string.format("TARGET dead")) -return self -end -function TARGET:OnEventUnitDeadOrLost(EventData) -local Name=EventData and EventData.IniUnitName or nil -if self:IsElement(Name)and not self:IsCasualty(Name)then -self:T(self.lid..string.format("EVENT ID=%d: Unit %s dead or lost!",EventData.id,tostring(Name))) -table.insert(self.casualties,Name) -local target=self:GetTargetByName(EventData.IniGroupName) -if not target then -target=self:GetTargetByName(EventData.IniUnitName) -end -if target then -if EventData.id==EVENTS.RemoveUnit then -target.Ndead=target.Ndead+1 -else -target.Ndestroyed=target.Ndestroyed+1 -target.Ndead=target.Ndead+1 -end -if target.Ndead==target.N0 then -if target.Ndestroyed>=target.N0 then -self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed",EventData.id,tostring(target.Name))) -target.Life=0 -self:ObjectDestroyed(target) -else -self:T2(self.lid..string.format("EVENT ID=%d: target %s removed ==> dead",EventData.id,tostring(target.Name))) -target.Life=0 -self:ObjectDead(target) -end -end -end -end -return self -end -function TARGET:_AddObject(Object) -local target={} -target.N0=0 -target.Ndead=0 -target.Ndestroyed=0 -if Object:IsInstanceOf("GROUP")then -local group=Object -target.Type=TARGET.ObjectType.GROUP -target.Name=group:GetName() -target.Coordinate=group:GetCoordinate() -local units=group:GetUnits() -target.Life=0;target.Life0=0 -for _,_unit in pairs(units or{})do -local unit=_unit -local life=unit:GetLife() -target.Life=target.Life+life -target.Life0=target.Life0+math.max(unit:GetLife0(),life) -self.threatlevel0=self.threatlevel0+unit:GetThreatLevel() -table.insert(self.elements,unit:GetName()) -target.N0=target.N0+1 -end -elseif Object:IsInstanceOf("UNIT")then -local unit=Object -target.Type=TARGET.ObjectType.UNIT -target.Name=unit:GetName() -target.Coordinate=unit:GetCoordinate() -if unit then -target.Life=unit:GetLife() -target.Life0=math.max(unit:GetLife0(),target.Life) -self.threatlevel0=self.threatlevel0+unit:GetThreatLevel() -table.insert(self.elements,unit:GetName()) -target.N0=target.N0+1 -end -elseif Object:IsInstanceOf("STATIC")then -local static=Object -target.Type=TARGET.ObjectType.STATIC -target.Name=static:GetName() -target.Coordinate=static:GetCoordinate() -if static and static:IsAlive()then -target.Life0=static:GetLife0() -target.Life=static:GetLife() -target.N0=target.N0+1 -table.insert(self.elements,target.Name) -end -elseif Object:IsInstanceOf("SCENERY")then -local scenery=Object -target.Type=TARGET.ObjectType.SCENERY -target.Name=scenery:GetName() -target.Coordinate=scenery:GetCoordinate() -target.Life0=scenery:GetLife0() -if target.Life0==0 then target.Life0=1 end -target.Life=scenery:GetLife() -target.N0=target.N0+1 -table.insert(self.elements,target.Name) -elseif Object:IsInstanceOf("AIRBASE")then -local airbase=Object -target.Type=TARGET.ObjectType.AIRBASE -target.Name=airbase:GetName() -target.Coordinate=airbase:GetCoordinate() -target.Life0=1 -target.Life=1 -target.N0=target.N0+1 -table.insert(self.elements,target.Name) -elseif Object:IsInstanceOf("COORDINATE")then -local coord=UTILS.DeepCopy(Object) -target.Type=TARGET.ObjectType.COORDINATE -target.Name=coord:ToStringMGRS() -target.Coordinate=coord -target.Life0=1 -target.Life=1 -elseif Object:IsInstanceOf("ZONE_BASE")then -local zone=Object -Object=zone -target.Type=TARGET.ObjectType.ZONE -target.Name=zone:GetName() -target.Coordinate=zone:GetCoordinate() -target.Life0=1 -target.Life=1 -elseif Object:IsInstanceOf("OPSZONE")then -local zone=Object -Object=zone -target.Type=TARGET.ObjectType.OPSZONE -target.Name=zone:GetName() -target.Coordinate=zone:GetCoordinate() -target.N0=target.N0+1 -target.Life0=1 -target.Life=1 -else -self:E(self.lid.."ERROR: Unknown object type!") -return nil -end -self.life=self.life+target.Life -self.life0=self.life0+target.Life0 -self.N0=self.N0+target.N0 -self.Ntargets0=self.Ntargets0+1 -self.targetcounter=self.targetcounter+1 -target.ID=self.targetcounter -target.Status=TARGET.ObjectStatus.ALIVE -target.Object=Object -table.insert(self.targets,target) -if self.name==nil then -self.name=self:GetTargetName(target) -end -if self.category==nil then -self.category=self:GetTargetCategory(target) -end -return self -end -function TARGET:GetLife0() -return self.life0 -end -function TARGET:GetDamage() -local life=self:GetLife()/self:GetLife0() -local damage=1-life -return damage*100 -end -function TARGET:GetTargetLife(Target) -if Target.Type==TARGET.ObjectType.GROUP then -if Target.Object and Target.Object:IsAlive()then -local units=Target.Object:GetUnits() -local life=0 -for _,_unit in pairs(units or{})do -local unit=_unit -life=life+unit:GetLife() -end -return life -else -return 0 -end -elseif Target.Type==TARGET.ObjectType.UNIT then -local unit=Target.Object -if unit and unit:IsAlive()then -local life=unit:GetLife() -return life -else -return 0 -end -elseif Target.Type==TARGET.ObjectType.STATIC then -if Target.Object and Target.Object:IsAlive()then -local life=Target.Object:GetLife() -return life -else -return 0 -end -elseif Target.Type==TARGET.ObjectType.SCENERY then -if Target.Object and Target.Object:IsAlive(25)then -local life=Target.Object:GetLife() -return life -else -return 0 -end -elseif Target.Type==TARGET.ObjectType.AIRBASE then -if Target.Status==TARGET.ObjectStatus.ALIVE then -return 1 -else -return 0 -end -elseif Target.Type==TARGET.ObjectType.COORDINATE then -return 1 -elseif Target.Type==TARGET.ObjectType.ZONE or Target.Type==TARGET.ObjectType.OPSZONE then -return 1 -else -self:E("ERROR: unknown target object type in GetTargetLife!") -end -return self -end -function TARGET:GetLife() -local N=0 -for _,_target in pairs(self.targets)do -local Target=_target -N=N+self:GetTargetLife(Target) -end -return N -end -function TARGET:GetTargetThreatLevelMax(Target) -if Target.Type==TARGET.ObjectType.GROUP then -local group=Target.Object -if group and group:IsAlive()then -local tl=group:GetThreatLevel() -return tl -else -return 0 -end -elseif Target.Type==TARGET.ObjectType.UNIT then -local unit=Target.Object -if unit and unit:IsAlive()then -local life=unit:GetThreatLevel() -return life -else -return 0 -end -elseif Target.Type==TARGET.ObjectType.STATIC then -return 0 -elseif Target.Type==TARGET.ObjectType.SCENERY then -return 0 -elseif Target.Type==TARGET.ObjectType.AIRBASE then -return 0 -elseif Target.Type==TARGET.ObjectType.COORDINATE then -return 0 -elseif Target.Type==TARGET.ObjectType.ZONE then -return 0 -else -self:E("ERROR: unknown target object type in GetTargetThreatLevel!") -end -return self -end -function TARGET:GetThreatLevelMax() -local N=0 -for _,_target in pairs(self.targets)do -local Target=_target -local n=self:GetTargetThreatLevelMax(Target) -if n>N then -N=n -end -end -return N -end -function TARGET:GetTargetVec2(Target) -local vec3=self:GetTargetVec3(Target) -if vec3 then -return{x=vec3.x,y=vec3.z} -end -return nil -end -function TARGET:GetTargetVec3(Target,Average) -if Target.Type==TARGET.ObjectType.GROUP then -local object=Target.Object -if object and object:IsAlive()then -local vec3=object:GetVec3() -if Average then -vec3=object:GetAverageVec3() -end -if vec3 then -return vec3 -else -return nil -end -else -return nil -end -elseif Target.Type==TARGET.ObjectType.UNIT then -local object=Target.Object -if object and object:IsAlive()then -local vec3=object:GetVec3() -return vec3 -else -return nil -end -elseif Target.Type==TARGET.ObjectType.STATIC then -local object=Target.Object -if object and object:IsAlive()then -local vec3=object:GetVec3() -return vec3 -else -return nil -end -elseif Target.Type==TARGET.ObjectType.SCENERY then -local object=Target.Object -if object then -local vec3=object:GetVec3() -return vec3 -else -return nil -end -elseif Target.Type==TARGET.ObjectType.AIRBASE then -local object=Target.Object -local vec3=object:GetVec3() -return vec3 -elseif Target.Type==TARGET.ObjectType.COORDINATE then -local object=Target.Object -local vec3={x=object.x,y=object.y,z=object.z} -return vec3 -elseif Target.Type==TARGET.ObjectType.ZONE then -local object=Target.Object -local vec3=object:GetVec3() -return vec3 -elseif Target.Type==TARGET.ObjectType.OPSZONE then -local object=Target.Object -local vec3=object:GetZone():GetVec3() -return vec3 -end -self:E(self.lid.."ERROR: Unknown TARGET type! Cannot get Vec3") -end -function TARGET:GetTargetHeading(Target) -if Target.Type==TARGET.ObjectType.GROUP then -local object=Target.Object -if object and object:IsAlive()then -local heading=object:GetHeading() -if heading then -return heading -else -return nil -end -else -return nil -end -elseif Target.Type==TARGET.ObjectType.UNIT then -local object=Target.Object -if object and object:IsAlive()then -local heading=object:GetHeading() -return heading -else -return nil -end -elseif Target.Type==TARGET.ObjectType.STATIC then -local object=Target.Object -if object and object:IsAlive()then -local heading=object:GetHeading() -return heading -else -return nil -end -elseif Target.Type==TARGET.ObjectType.SCENERY then -local object=Target.Object -if object then -local heading=object:GetHeading() -return heading -else -return nil -end -elseif Target.Type==TARGET.ObjectType.AIRBASE then -local object=Target.Object -return 0 -elseif Target.Type==TARGET.ObjectType.COORDINATE then -local object=Target.Object -return 0 -elseif Target.Type==TARGET.ObjectType.ZONE or Target.Type==TARGET.ObjectType.OPSZONE then -local object=Target.Object -return 0 -end -self:E(self.lid.."ERROR: Unknown TARGET type! Cannot get heading") -end -function TARGET:GetTargetCoordinate(Target,Average) -if Target.Type==TARGET.ObjectType.COORDINATE then -return Target.Object -else -local vec3=self:GetTargetVec3(Target,Average) -if vec3 then -Target.Coordinate.x=vec3.x -Target.Coordinate.y=vec3.y -Target.Coordinate.z=vec3.z -end -return Target.Coordinate -end -return nil -end -function TARGET:GetTargetName(Target) -if Target.Type==TARGET.ObjectType.GROUP then -if Target.Object and Target.Object:IsAlive()then -return Target.Object:GetName() -end -elseif Target.Type==TARGET.ObjectType.UNIT then -if Target.Object and Target.Object:IsAlive()then -return Target.Object:GetName() -end -elseif Target.Type==TARGET.ObjectType.STATIC then -if Target.Object and Target.Object:IsAlive()then -return Target.Object:GetName() -end -elseif Target.Type==TARGET.ObjectType.AIRBASE then -if Target.Status==TARGET.ObjectStatus.ALIVE then -return Target.Object:GetName() -end -elseif Target.Type==TARGET.ObjectType.COORDINATE then -local coord=Target.Object -return coord:ToStringMGRS() -elseif Target.Type==TARGET.ObjectType.ZONE then -local Zone=Target.Object -return Zone:GetName() -elseif Target.Type==TARGET.ObjectType.SCENERY then -local Zone=Target.Object -return Zone:GetName() -end -return"Unknown" -end -function TARGET:GetName() -local name=self.name or"Unknown" -return name -end -function TARGET:GetVec2() -for _,_target in pairs(self.targets)do -local Target=_target -local coordinate=self:GetTargetVec2(Target) -if coordinate then -return coordinate -end -end -self:E(self.lid..string.format("ERROR: Cannot get Vec2 of target %s",self.name)) -return nil -end -function TARGET:GetVec3() -for _,_target in pairs(self.targets)do -local Target=_target -local coordinate=self:GetTargetVec3(Target) -if coordinate then -return coordinate -end -end -self:E(self.lid..string.format("ERROR: Cannot get Vec3 of target %s",self.name)) -return nil -end -function TARGET:GetCoordinate() -for _,_target in pairs(self.targets)do -local Target=_target -local coordinate=self:GetTargetCoordinate(Target) -if coordinate then -return coordinate -end -end -self:E(self.lid..string.format("ERROR: Cannot get coordinate of target %s",tostring(self.name))) -return nil -end -function TARGET:GetAverageCoordinate() -for _,_target in pairs(self.targets)do -local Target=_target -local coordinate=self:GetTargetCoordinate(Target,true) -if coordinate then -return coordinate -end -end -self:E(self.lid..string.format("ERROR: Cannot get average coordinate of target %s",tostring(self.name))) -return nil -end -function TARGET:GetHeading() -for _,_target in pairs(self.targets)do -local Target=_target -local heading=self:GetTargetHeading(Target) -if heading then -return heading -end -end -self:E(self.lid..string.format("ERROR: Cannot get heading of target %s",tostring(self.name))) -return nil -end -function TARGET:GetCategory() -return self.category -end -function TARGET:GetTargetCategory(Target) -local category=nil -if Target.Type==TARGET.ObjectType.GROUP then -if Target.Object and Target.Object:IsAlive()~=nil then -local group=Target.Object -local cat=group:GetCategory() -if cat==Group.Category.AIRPLANE or cat==Group.Category.HELICOPTER then -category=TARGET.Category.AIRCRAFT -elseif cat==Group.Category.GROUND or cat==Group.Category.TRAIN then -category=TARGET.Category.GROUND -elseif cat==Group.Category.SHIP then -category=TARGET.Category.NAVAL -end -end -elseif Target.Type==TARGET.ObjectType.UNIT then -if Target.Object and Target.Object:IsAlive()~=nil then -local unit=Target.Object -local group=unit:GetGroup() -local cat=group:GetCategory() -if cat==Group.Category.AIRPLANE or cat==Group.Category.HELICOPTER then -category=TARGET.Category.AIRCRAFT -elseif cat==Group.Category.GROUND or cat==Group.Category.TRAIN then -category=TARGET.Category.GROUND -elseif cat==Group.Category.SHIP then -category=TARGET.Category.NAVAL -end -end -elseif Target.Type==TARGET.ObjectType.STATIC then -return TARGET.Category.GROUND -elseif Target.Type==TARGET.ObjectType.SCENERY then -return TARGET.Category.GROUND -elseif Target.Type==TARGET.ObjectType.AIRBASE then -return TARGET.Category.AIRBASE -elseif Target.Type==TARGET.ObjectType.COORDINATE then -return TARGET.Category.COORDINATE -elseif Target.Type==TARGET.ObjectType.ZONE then -return TARGET.Category.ZONE -elseif Target.Type==TARGET.ObjectType.OPSZONE then -return TARGET.Category.OPSZONE -else -self:E("ERROR: unknown target category!") -end -return category -end -function TARGET:GetTargetCoalition(Target) -local coal=coalition.side.NEUTRAL -if Target.Type==TARGET.ObjectType.GROUP then -if Target.Object and Target.Object:IsAlive()~=nil then -local object=Target.Object -coal=object:GetCoalition() -end -elseif Target.Type==TARGET.ObjectType.UNIT then -if Target.Object and Target.Object:IsAlive()~=nil then -local object=Target.Object -coal=object:GetCoalition() -end -elseif Target.Type==TARGET.ObjectType.STATIC then -local object=Target.Object -coal=object:GetCoalition() -elseif Target.Type==TARGET.ObjectType.SCENERY then -elseif Target.Type==TARGET.ObjectType.AIRBASE then -local object=Target.Object -coal=object:GetCoalition() -elseif Target.Type==TARGET.ObjectType.COORDINATE then -elseif Target.Type==TARGET.ObjectType.ZONE then -elseif Target.Type==TARGET.ObjectType.OPSZONE then -local object=Target.Object -coal=object:GetOwner() -else -self:E("ERROR: unknown target category!") -end -return coal -end -function TARGET:GetTargetByName(ObjectName) -for _,_target in pairs(self.targets)do -local target=_target -if ObjectName==target.Name then -return target -end -end -return nil -end -function TARGET:GetObjective(RefCoordinate,Coalitions) -if RefCoordinate then -local dmin=math.huge -local tmin=nil -for _,_target in pairs(self.targets)do -local target=_target -if target.Status~=TARGET.ObjectStatus.DEAD and(Coalitions==nil or UTILS.IsInTable(UTILS.EnsureTable(Coalitions),self:GetTargetCoalition(target)))then -local vec3=self:GetTargetVec3(target) -local d=UTILS.VecDist3D(vec3,RefCoordinate) -if d1 then -if Coalitions==nil or UTILS.IsInTable(UTILS.EnsureTable(Coalitions),unit:GetCoalition())then -N=N+1 -end -end -end -elseif Target.Type==TARGET.ObjectType.UNIT then -local target=Target.Object -if target and target:IsAlive()~=nil and target:GetLife()>1 then -if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetCoalition())then -N=N+1 -end -end -elseif Target.Type==TARGET.ObjectType.STATIC then -local target=Target.Object -if target and target:IsAlive()then -if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetCoalition())then -N=N+1 -end -end -elseif Target.Type==TARGET.ObjectType.SCENERY then -if Target.Status~=TARGET.ObjectStatus.DEAD then -N=N+1 -end -elseif Target.Type==TARGET.ObjectType.AIRBASE then -local target=Target.Object -if Target.Status==TARGET.ObjectStatus.ALIVE then -if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetCoalition())then -N=N+1 -end -end -elseif Target.Type==TARGET.ObjectType.COORDINATE then -elseif Target.Type==TARGET.ObjectType.ZONE then -elseif Target.Type==TARGET.ObjectType.OPSZONE then -local target=Target.Object -if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetOwner())then -N=N+1 -end -else -self:E(self.lid.."ERROR: Unknown target type! Cannot count targets") -end -return N -end -function TARGET:CountTargets(Coalitions) -local N=0 -for _,_target in pairs(self.targets)do -local Target=_target -N=N+self:CountObjectives(Target,Coalitions) -end -return N -end -function TARGET:IsElement(Name) -if Name==nil then -return false -end -for _,name in pairs(self.elements)do -if name==Name then -return true -end -end -return false -end -function TARGET:IsCasualty(Name) -if Name==nil then -return false -end -for _,name in pairs(self.casualties)do -if tostring(name)==tostring(Name)then -return true -end -end -return false -end -EASYGCICAP={ -ClassName="EASYGCICAP", -overhead=0.75, -capgrouping=2, -airbasename=nil, -airbase=nil, -coalition="blue", -alias=nil, -wings={}, -Intel=nil, -resurrection=900, -capspeed=300, -capalt=25000, -capdir=45, -capleg=15, -maxinterceptsize=2, -missionrange=100, -noaltert5=4, -ManagedAW={}, -ManagedSQ={}, -ManagedCP={}, -ManagedTK={}, -ManagedEWR={}, -ManagedREC={}, -MaxAliveMissions=8, -debug=false, -engagerange=50, -repeatsonfailure=3, -GoZoneSet=nil, -NoGoZoneSet=nil, -Monitor=false, -TankerInvisible=true, -CapFormation=nil, -ReadyFlightGroups={}, -} -EASYGCICAP.version="0.1.10" -function EASYGCICAP:New(Alias,AirbaseName,Coalition,EWRName) -local self=BASE:Inherit(self,FSM:New()) -self.alias=Alias or AirbaseName.." CAP Wing" -self.coalitionname=string.lower(Coalition)or"blue" -self.coalition=self.coaltitionname=="blue"and coalition.side.BLUE or coalition.side.RED -self.wings={} -self.EWRName=EWRName or self.coalitionname.." EWR" -self.airbasename=AirbaseName -self.airbase=AIRBASE:FindByName(self.airbasename) -self.GoZoneSet=SET_ZONE:New() -self.NoGoZoneSet=SET_ZONE:New() -self.resurrection=900 -self.capspeed=300 -self.capalt=25000 -self.capdir=90 -self.capleg=15 -self.capgrouping=2 -self.missionrange=100 -self.noaltert5=2 -self.MaxAliveMissions=8 -self.engagerange=50 -self.repeatsonfailure=3 -self.Monitor=false -self.TankerInvisible=true -self.CapFormation=ENUMS.Formation.FixedWing.FingerFour.Group -self.lid=string.format("EASYGCICAP %s | ",self.alias) -self:SetStartState("Stopped") -self:AddTransition("Stopped","Start","Running") -self:AddTransition("Running","Stop","Stopped") -self:AddTransition("*","Status","*") -self:AddAirwing(self.airbasename,self.alias,self.CapZoneName) -self:I(self.lid.."Created new instance (v"..self.version..")") -self:__Start(math.random(6,12)) -return self -end -function EASYGCICAP:SetCAPFormation(Formation) -self:T(self.lid.."SetCAPFormation") -self.CapFormation=Formation -return self -end -function EASYGCICAP:SetTankerAndAWACSInvisible(Switch) -self:T(self.lid.."SetTankerAndAWACSInvisible") -self.TankerInvisible=Switch -return self -end -function EASYGCICAP:SetMaxAliveMissions(Maxiumum) -self:T(self.lid.."SetDefaultResurrection") -self.MaxAliveMissions=Maxiumum or 8 -return self -end -function EASYGCICAP:SetDefaultResurrection(Seconds) -self:T(self.lid.."SetDefaultResurrection") -self.resurrection=Seconds or 900 -return self -end -function EASYGCICAP:SetDefaultRepeatOnFailure(Retries) -self:T(self.lid.."SetDefaultRepeatOnFailure") -self.repeatsonfailure=Retries or 3 -return self -end -function EASYGCICAP:SetDefaultCAPSpeed(Speed) -self:T(self.lid.."SetDefaultSpeed") -self.capspeed=Speed or 300 -return self -end -function EASYGCICAP:SetDefaultCAPAlt(Altitude) -self:T(self.lid.."SetDefaultAltitude") -self.capalt=Altitude or 25000 -return self -end -function EASYGCICAP:SetDefaultCAPDirection(Direction) -self:T(self.lid.."SetDefaultDirection") -self.capdir=Direction or 90 -return self -end -function EASYGCICAP:SetDefaultCAPLeg(Leg) -self:T(self.lid.."SetDefaultLeg") -self.capleg=Leg or 15 -return self -end -function EASYGCICAP:SetDefaultCAPGrouping(Grouping) -self:T(self.lid.."SetDefaultCAPGrouping") -self.capgrouping=Grouping or 2 -return self -end -function EASYGCICAP:SetDefaultMissionRange(Range) -self:T(self.lid.."SetDefaultMissionRange") -self.missionrange=Range or 100 -return self -end -function EASYGCICAP:SetDefaultNumberAlter5Standby(Airframes) -self:T(self.lid.."SetDefaultNumberAlter5Standby") -self.noaltert5=math.abs(Airframes)or 2 -return self -end -function EASYGCICAP:SetDefaultEngageRange(Range) -self:T(self.lid.."SetDefaultNumberAlter5Standby") -self.engagerange=Range or 50 -return self -end -function EASYGCICAP:SetDefaultOverhead(Overhead) -self:T(self.lid.."SetDefaultOverhead") -self.overhead=Overhead or 0.75 -return self -end -function EASYGCICAP:SetCapStartTimeVariation(Start,End) -self.capOptionVaryStartTime=Start or 5 -self.capOptionVaryEndTime=End or 60 -return self -end -function EASYGCICAP:AddAirwing(Airbasename,Alias) -self:T(self.lid.."AddAirwing "..Airbasename) -local AWEntry={} -AWEntry.AirbaseName=Airbasename -AWEntry.Alias=Alias -self.ManagedAW[Airbasename]=AWEntry -return self -end -function EASYGCICAP:_CreateAirwings() -self:T(self.lid.."_CreateAirwings") -for airbase,data in pairs(self.ManagedAW)do -local wing=data -local afb=wing.AirbaseName -local alias=wing.Alias -self:_AddAirwing(airbase,alias) -end -return self -end -function EASYGCICAP:_AddAirwing(Airbasename,Alias) -self:T(self.lid.."_AddAirwing "..Airbasename) -local CapFormation=self.CapFormation -local CAP_Wing=AIRWING:New(Airbasename,Alias) -CAP_Wing:SetVerbosityLevel(0) -CAP_Wing:SetReportOff() -CAP_Wing:SetMarker(false) -CAP_Wing:SetAirbase(AIRBASE:FindByName(Airbasename)) -CAP_Wing:SetRespawnAfterDestroyed() -CAP_Wing:SetNumberCAP(self.capgrouping) -CAP_Wing:SetCapCloseRaceTrack(true) -if self.capOptionVaryStartTime then -CAP_Wing:SetCapStartTimeVariation(self.capOptionVaryStartTime,self.capOptionVaryEndTime) -end -if CapFormation then -CAP_Wing:SetCAPFormation(CapFormation) -end -if#self.ManagedTK>0 then -CAP_Wing:SetNumberTankerBoom(1) -CAP_Wing:SetNumberTankerProbe(1) -end -if#self.ManagedEWR>0 then -CAP_Wing:SetNumberAWACS(1) -end -if#self.ManagedREC>0 then -CAP_Wing:SetNumberRecon(1) -end -CAP_Wing:SetTakeoffHot() -CAP_Wing:SetLowFuelThreshold(0.3) -CAP_Wing.RandomAssetScore=math.random(50,100) -CAP_Wing:Start() -local Intel=self.Intel -local TankerInvisible=self.TankerInvisible -function CAP_Wing:OnAfterFlightOnMission(From,Event,To,Flightgroup,Mission) -local flightgroup=Flightgroup -flightgroup:SetDespawnAfterHolding() -flightgroup:SetDestinationbase(AIRBASE:FindByName(Airbasename)) -flightgroup:GetGroup():CommandEPLRS(true,5) -flightgroup:GetGroup():SetOptionRadarUsingForContinousSearch() -if Mission.type~=AUFTRAG.Type.TANKER and Mission.type~=AUFTRAG.Type.AWACS and Mission.type~=AUFTRAG.Type.RECON then -flightgroup:SetDetection(true) -flightgroup:SetEngageDetectedOn(self.engagerange,{"Air"},self.GoZoneSet,self.NoGoZoneSet) -flightgroup:SetOutOfAAMRTB() -if CapFormation then -flightgroup:GetGroup():SetOption(AI.Option.Air.id.FORMATION,CapFormation) -end -end -if Mission.type==AUFTRAG.Type.TANKER or Mission.type==AUFTRAG.Type.AWACS or Mission.type==AUFTRAG.Type.RECON then -if TankerInvisible then -flightgroup:GetGroup():SetCommandInvisible(true) -end -if Mission.type==AUFTRAG.Type.RECON then -flightgroup:SetDetection(true) -end -end -flightgroup:GetGroup():OptionROTEvadeFire() -flightgroup:SetFuelLowRTB(true) -Intel:AddAgent(flightgroup) -function flightgroup:OnAfterHolding(From,Event,To) -self:Despawn(1,true) -end -end -if self.noaltert5>0 then -local alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) -alert:SetRequiredAssets(self.noaltert5) -alert:SetRepeat(99) -CAP_Wing:AddMission(alert) -end -self.wings[Airbasename]={CAP_Wing,AIRBASE:FindByName(Airbasename):GetZone(),Airbasename} -return self -end -function EASYGCICAP:AddPatrolPointCAP(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) -self:T(self.lid.."AddPatrolPointCAP "..Coordinate:ToStringLLDDM()) -local EntryCAP={} -EntryCAP.AirbaseName=AirbaseName -EntryCAP.Coordinate=Coordinate -EntryCAP.Altitude=Altitude or 25000 -EntryCAP.Speed=Speed or 300 -EntryCAP.Heading=Heading or 90 -EntryCAP.LegLength=LegLength or 15 -self.ManagedCP[#self.ManagedCP+1]=EntryCAP -if self.debug then -local mark=MARKER:New(Coordinate,self.lid.."Patrol Point"):ToAll() -end -return self -end -function EASYGCICAP:AddPatrolPointRecon(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) -self:T(self.lid.."AddPatrolPointRecon "..Coordinate:ToStringLLDDM()) -local EntryCAP={} -EntryCAP.AirbaseName=AirbaseName -EntryCAP.Coordinate=Coordinate -EntryCAP.Altitude=Altitude or 25000 -EntryCAP.Speed=Speed or 300 -EntryCAP.Heading=Heading or 90 -EntryCAP.LegLength=LegLength or 15 -self.ManagedREC[#self.ManagedREC+1]=EntryCAP -if self.debug then -local mark=MARKER:New(Coordinate,self.lid.."Patrol Point Recon"):ToAll() -end -return self -end -function EASYGCICAP:AddPatrolPointTanker(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) -self:T(self.lid.."AddPatrolPointTanker "..Coordinate:ToStringLLDDM()) -local EntryCAP={} -EntryCAP.AirbaseName=AirbaseName -EntryCAP.Coordinate=Coordinate -EntryCAP.Altitude=Altitude or 25000 -EntryCAP.Speed=Speed or 300 -EntryCAP.Heading=Heading or 90 -EntryCAP.LegLength=LegLength or 15 -self.ManagedTK[#self.ManagedTK+1]=EntryCAP -if self.debug then -local mark=MARKER:New(Coordinate,self.lid.."Patrol Point Tanker"):ToAll() -end -return self -end -function EASYGCICAP:AddPatrolPointAwacs(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) -self:T(self.lid.."AddPatrolPointAwacs "..Coordinate:ToStringLLDDM()) -local EntryCAP={} -EntryCAP.AirbaseName=AirbaseName -EntryCAP.Coordinate=Coordinate -EntryCAP.Altitude=Altitude or 25000 -EntryCAP.Speed=Speed or 300 -EntryCAP.Heading=Heading or 90 -EntryCAP.LegLength=LegLength or 15 -self.ManagedEWR[#self.ManagedEWR+1]=EntryCAP -if self.debug then -local mark=MARKER:New(Coordinate,self.lid.."Patrol Point AWACS"):ToAll() -end -return self -end -function EASYGCICAP:_SetTankerPatrolPoints() -self:T(self.lid.."_SetTankerPatrolPoints") -for _,_data in pairs(self.ManagedTK)do -local data=_data -local Wing=self.wings[data.AirbaseName][1] -local Coordinate=data.Coordinate -local Altitude=data.Altitude -local Speed=data.Speed -local Heading=data.Heading -local LegLength=data.LegLength -Wing:AddPatrolPointTANKER(Coordinate,Altitude,Speed,Heading,LegLength) -end -return self -end -function EASYGCICAP:_SetAwacsPatrolPoints() -self:T(self.lid.."_SetAwacsPatrolPoints") -for _,_data in pairs(self.ManagedEWR)do -local data=_data -local Wing=self.wings[data.AirbaseName][1] -local Coordinate=data.Coordinate -local Altitude=data.Altitude -local Speed=data.Speed -local Heading=data.Heading -local LegLength=data.LegLength -Wing:AddPatrolPointAWACS(Coordinate,Altitude,Speed,Heading,LegLength) -end -return self -end -function EASYGCICAP:_SetCAPPatrolPoints() -self:T(self.lid.."_SetCAPPatrolPoints") -for _,_data in pairs(self.ManagedCP)do -local data=_data -local Wing=self.wings[data.AirbaseName][1] -local Coordinate=data.Coordinate -local Altitude=data.Altitude -local Speed=data.Speed -local Heading=data.Heading -local LegLength=data.LegLength -Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength) -end -return self -end -function EASYGCICAP:_SetReconPatrolPoints() -self:T(self.lid.."_SetReconPatrolPoints") -for _,_data in pairs(self.ManagedREC)do -local data=_data -local Wing=self.wings[data.AirbaseName][1] -local Coordinate=data.Coordinate -local Altitude=data.Altitude -local Speed=data.Speed -local Heading=data.Heading -local LegLength=data.LegLength -Wing:AddPatrolPointRecon(Coordinate,Altitude,Speed,Heading,LegLength) -end -return self -end -function EASYGCICAP:_CreateSquads() -self:T(self.lid.."_CreateSquads") -for name,data in pairs(self.ManagedSQ)do -local squad=data -local SquadName=name -local TemplateName=squad.TemplateName -local AirbaseName=squad.AirbaseName -local AirFrames=squad.AirFrames -local Skill=squad.Skill -local Modex=squad.Modex -local Livery=squad.Livery -local Frequeny=squad.Frequency -local Modulation=squad.Modulation -local TACAN=squad.TACAN -if squad.Tanker then -self:_AddTankerSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequeny,Modulation,TACAN) -elseif squad.AWACS then -self:_AddAWACSSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequeny,Modulation) -elseif squad.RECON then -self:_AddReconSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery) -else -self:_AddSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery) -end -end -return self -end -function EASYGCICAP:AddSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery) -self:T(self.lid.."AddSquadron "..SquadName) -local EntrySQ={} -EntrySQ.TemplateName=TemplateName -EntrySQ.SquadName=SquadName -EntrySQ.AirbaseName=AirbaseName -EntrySQ.AirFrames=AirFrames or 20 -EntrySQ.Skill=Skill or AI.Skill.AVERAGE -EntrySQ.Modex=Modex or 402 -EntrySQ.Livery=Livery -self.ManagedSQ[SquadName]=EntrySQ -return self -end -function EASYGCICAP:AddReconSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery) -self:T(self.lid.."AddReconSquadron "..SquadName) -local EntrySQ={} -EntrySQ.TemplateName=TemplateName -EntrySQ.SquadName=SquadName -EntrySQ.AirbaseName=AirbaseName -EntrySQ.AirFrames=AirFrames or 20 -EntrySQ.Skill=Skill or AI.Skill.AVERAGE -EntrySQ.Modex=Modex or 402 -EntrySQ.Livery=Livery -EntrySQ.RECON=true -self.ManagedSQ[SquadName]=EntrySQ -return self -end -function EASYGCICAP:AddTankerSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation,TACAN) -self:T(self.lid.."AddTankerSquadron "..SquadName) -local EntrySQ={} -EntrySQ.TemplateName=TemplateName -EntrySQ.SquadName=SquadName -EntrySQ.AirbaseName=AirbaseName -EntrySQ.AirFrames=AirFrames or 20 -EntrySQ.Skill=Skill or AI.Skill.AVERAGE -EntrySQ.Modex=Modex or 602 -EntrySQ.Livery=Livery -EntrySQ.Frequency=Frequency -EntrySQ.Modulation=Livery -EntrySQ.TACAN=TACAN -EntrySQ.Tanker=true -self.ManagedSQ[SquadName]=EntrySQ -return self -end -function EASYGCICAP:AddAWACSSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation) -self:T(self.lid.."AddAWACSSquadron "..SquadName) -local EntrySQ={} -EntrySQ.TemplateName=TemplateName -EntrySQ.SquadName=SquadName -EntrySQ.AirbaseName=AirbaseName -EntrySQ.AirFrames=AirFrames or 20 -EntrySQ.Skill=Skill or AI.Skill.AVERAGE -EntrySQ.Modex=Modex or 702 -EntrySQ.Livery=Livery -EntrySQ.Frequency=Frequency -EntrySQ.Modulation=Livery -EntrySQ.AWACS=true -self.ManagedSQ[SquadName]=EntrySQ -return self -end -function EASYGCICAP:_AddSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation) -self:T(self.lid.."_AddSquadron "..SquadName) -local Squadron_One=SQUADRON:New(TemplateName,AirFrames,SquadName) -Squadron_One:AddMissionCapability({AUFTRAG.Type.CAP,AUFTRAG.Type.GCICAP,AUFTRAG.Type.INTERCEPT,AUFTRAG.Type.PATROLRACETRACK,AUFTRAG.Type.ALERT5}) -Squadron_One:SetFuelLowThreshold(0.3) -Squadron_One:SetTurnoverTime(10,20) -Squadron_One:SetModex(Modex) -Squadron_One:SetLivery(Livery) -Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) -Squadron_One:SetMissionRange(self.missionrange) -local wing=self.wings[AirbaseName][1] -wing:AddSquadron(Squadron_One) -wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.CAP,AUFTRAG.Type.GCICAP,AUFTRAG.Type.INTERCEPT,AUFTRAG.Type.PATROLRACETRACK,AUFTRAG.Type.ALERT5},75) -return self -end -function EASYGCICAP:_AddReconSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery) -self:T(self.lid.."_AddReconSquadron "..SquadName) -local Squadron_One=SQUADRON:New(TemplateName,AirFrames,SquadName) -Squadron_One:AddMissionCapability({AUFTRAG.Type.RECON}) -Squadron_One:SetFuelLowThreshold(0.3) -Squadron_One:SetTurnoverTime(10,20) -Squadron_One:SetModex(Modex) -Squadron_One:SetLivery(Livery) -Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) -Squadron_One:SetMissionRange(self.missionrange) -local wing=self.wings[AirbaseName][1] -wing:AddSquadron(Squadron_One) -wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.RECON},75) -return self -end -function EASYGCICAP:_AddTankerSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation,TACAN) -self:T(self.lid.."_AddTankerSquadron "..SquadName) -local Squadron_One=SQUADRON:New(TemplateName,AirFrames,SquadName) -Squadron_One:AddMissionCapability({AUFTRAG.Type.TANKER}) -Squadron_One:SetFuelLowThreshold(0.3) -Squadron_One:SetTurnoverTime(10,20) -Squadron_One:SetModex(Modex) -Squadron_One:SetLivery(Livery) -Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) -Squadron_One:SetMissionRange(self.missionrange) -Squadron_One:SetRadio(Frequency,Modulation) -Squadron_One:AddTacanChannel(TACAN,TACAN) -local wing=self.wings[AirbaseName][1] -wing:AddSquadron(Squadron_One) -wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.TANKER},75) -return self -end -function EASYGCICAP:_AddAWACSSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation) -self:T(self.lid.."_AddAWACSSquadron "..SquadName) -local Squadron_One=SQUADRON:New(TemplateName,AirFrames,SquadName) -Squadron_One:AddMissionCapability({AUFTRAG.Type.AWACS}) -Squadron_One:SetFuelLowThreshold(0.3) -Squadron_One:SetTurnoverTime(10,20) -Squadron_One:SetModex(Modex) -Squadron_One:SetLivery(Livery) -Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) -Squadron_One:SetMissionRange(self.missionrange) -Squadron_One:SetRadio(Frequency,Modulation) -local wing=self.wings[AirbaseName][1] -wing:AddSquadron(Squadron_One) -wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.AWACS},75) -return self -end -function EASYGCICAP:AddAcceptZone(Zone) -self:T(self.lid.."AddAcceptZone0") -self.GoZoneSet:AddZone(Zone) -return self -end -function EASYGCICAP:AddRejectZone(Zone) -self:T(self.lid.."AddRejectZone") -self.NoGoZoneSet:AddZone(Zone) -return self -end -function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize) -self:I("_TryAssignIntercept for size "..WingSize or 1) -local assigned=false -local wingsize=WingSize or 1 -local mindist=0 -local disttable={} -if Group and Group:IsAlive()then -local gcoord=Group:GetCoordinate()or COORDINATE:New(0,0,0) -self:I(self.lid..string.format("Assignment for %s",Group:GetName())) -for _name,_FG in pairs(ReadyFlightGroups or{})do -local FG=_FG -local fcoord=FG:GetCoordinate() -local dist=math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1)) -self:I(self.lid..string.format("FG %s Distance %dkm",_name,dist)) -disttable[#disttable+1]={FG=FG,dist=dist} -if dist>mindist then mindist=dist end -end -local function sortDistance(a,b) -return a.distmaxsize then wingsize=maxsize end -local retrymission=true -if Cluster.mission and(not Cluster.mission:IsOver())then -retrymission=false -end -if(retrymission)and(wingsize>=1)then -MESSAGE:New(string.format("**** %s Interceptors need wingsize %d",UTILS.GetCoalitionName(self.coalition),wingsize),15,"CAPGCI"):ToAllIf(self.debug):ToLog() -for _,_data in pairs(wings)do -local airwing=_data[1] -local zone=_data[2] -local zonecoord=zone:GetCoordinate() -local name=_data[3] -local distance=position:DistanceFromPointVec2(zonecoord) -local airframes=airwing:CountAssets(true) -if distance=wingsize then -bestdistance=distance -targetairwing=airwing -targetawname=name -end -end -for _,_data in pairs(ctlpts)do -local data=_data -local name=data.AirbaseName -local zonecoord=data.Coordinate -local airwing=wings[name][1] -local distance=position:DistanceFromPointVec2(zonecoord) -local airframes=airwing:CountAssets(true) -if distance=wingsize then -bestdistance=distance -targetairwing=airwing -targetawname=name -end -end -local text=string.format("Closest Airwing is %s",targetawname) -local m=MESSAGE:New(text,10,"CAPGCI"):ToAllIf(self.debug):ToLog() -if targetairwing then -local AssetCount=targetairwing:CountAssetsOnMission(MissionTypes,Cohort) -self:T(self.lid.." Assets on Mission "..AssetCount) -if AssetCount<=MaxAliveMissions then -local repeats=repeatsonfailure -local InterceptAuftrag=AUFTRAG:NewINTERCEPT(contact.group) -:SetMissionRange(150) -:SetPriority(1,true,1) -:SetRepeatOnFailure(repeats) -:SetMissionSpeed(UTILS.KnotsToAltKIAS(capspeed,capalt)) -:SetMissionAltitude(capalt) -if nogozoneset:Count()>0 then -InterceptAuftrag:AddConditionSuccess( -function(group,zoneset) -local success=false -if group and group:IsAlive()then -local coord=group:GetCoordinate() -if coord and zoneset:IsCoordinateInZone(coord)then -success=true -end -end -return success -end, -contact.group, -nogozoneset -) -end -local assigned,rest=self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) -if not assigned then -InterceptAuftrag:SetRequiredAssets(rest) -targetairwing:AddMission(InterceptAuftrag) -end -Cluster.mission=InterceptAuftrag -end -else -MESSAGE:New("**** Not enough airframes available or max mission limit reached!",15,"CAPGCI"):ToAllIf(self.debug):ToLog() -end -end -end -function EASYGCICAP:_StartIntel() -self:T(self.lid.."_StartIntel") -local BlueAir_DetectionSetGroup=SET_GROUP:New() -BlueAir_DetectionSetGroup:FilterPrefixes({self.EWRName}) -BlueAir_DetectionSetGroup:FilterStart() -local BlueIntel=INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname,self.EWRName) -BlueIntel:SetClusterAnalysis(true,false,false) -BlueIntel:SetForgetTime(300) -BlueIntel:SetAcceptZones(self.GoZoneSet) -BlueIntel:SetRejectZones(self.NoGoZoneSet) -BlueIntel:SetVerbosity(0) -BlueIntel:Start() -if self.debug then -BlueIntel.debug=true -end -local function AssignCluster(Cluster) -self:_AssignIntercept(Cluster) -end -function BlueIntel:OnAfterNewCluster(From,Event,To,Cluster) -AssignCluster(Cluster) -end -self.Intel=BlueIntel -return self -end -function EASYGCICAP:onafterStart(From,Event,To) -self:T({From,Event,To}) -self:_StartIntel() -self:_CreateAirwings() -self:_CreateSquads() -self:_SetCAPPatrolPoints() -self:_SetTankerPatrolPoints() -self:_SetAwacsPatrolPoints() -self:_SetReconPatrolPoints() -self:__Status(-10) -return self -end -function EASYGCICAP:onbeforeStatus(From,Event,To) -self:T({From,Event,To}) -if self:GetState()=="Stopped"then return false end -return self -end -function EASYGCICAP:onafterStatus(From,Event,To) -self:T({From,Event,To}) -local function counttable(tbl) -local count=0 -for _,_data in pairs(tbl)do -count=count+1 -end -return count -end -local wings=counttable(self.ManagedAW) -local squads=counttable(self.ManagedSQ) -local caps=counttable(self.ManagedCP) -local assets=0 -local instock=0 -local capmission=0 -local interceptmission=0 -local reconmission=0 -local awacsmission=0 -local tankermission=0 -for _,_wing in pairs(self.wings)do -local count=_wing[1]:CountAssetsOnMission(MissionTypes,Cohort) -local count2=_wing[1]:CountAssets(true,MissionTypes,Attributes) -capmission=capmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) -interceptmission=interceptmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.INTERCEPT}) -reconmission=reconmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON}) -awacsmission=awacsmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS}) -tankermission=tankermission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) -assets=assets+count -instock=instock+count2 -local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) -self.ReadyFlightGroups=nil -self.ReadyFlightGroups={} -for _,_asset in pairs(assetsonmission or{})do -local asset=_asset -local FG=asset.flightgroup -if FG then -local name=FG:GetName() -local engage=FG:IsEngaging() -local hasmissiles=FG:IsOutOfMissiles()==nil and true or false -local ready=hasmissiles and FG:IsFuelGood()and FG:IsAirborne() -if ready then -self.ReadyFlightGroups[name]=FG -end -end -end -end -if self.Monitor then -local threatcount=#self.Intel.Clusters or 0 -local text="GCICAP "..self.alias -text=text.."\nWings: "..wings.."\nSquads: "..squads.."\nCapPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock -text=text.."\nThreats: "..threatcount -text=text.."\nMissions: "..capmission+interceptmission -text=text.."\n - CAP: "..capmission -text=text.."\n - Intercept: "..interceptmission -text=text.."\n - AWACS: "..awacsmission -text=text.."\n - TANKER: "..tankermission -text=text.."\n - Recon: "..reconmission -MESSAGE:New(text,15,"GCICAP"):ToAll():ToLogIf(self.debug) -end -self:__Status(30) -return self -end -function EASYGCICAP:onafterStop(From,Event,To) -self:T({From,Event,To}) -self.Intel:Stop() -return self -end -AI_BALANCER={ -ClassName="AI_BALANCER", -PatrolZones={}, -AIGroups={}, -Earliest=5, -Latest=60, -} -function AI_BALANCER:New(SetClient,SpawnAI) -local self=BASE:Inherit(self,FSM_SET:New(SET_GROUP:New())) -self:SetStartState("None") -self:AddTransition("*","Monitor","Monitoring") -self:AddTransition("*","Spawn","Spawning") -self:AddTransition("Spawning","Spawned","Spawned") -self:AddTransition("*","Destroy","Destroying") -self:AddTransition("*","Return","Returning") -self.SetClient=SetClient -self.SetClient:FilterOnce() -self.SpawnAI=SpawnAI -self.SpawnQueue={} -self.ToNearestAirbase=false -self.ToHomeAirbase=false -self:__Monitor(1) -return self -end -function AI_BALANCER:InitSpawnInterval(Earliest,Latest) -self.Earliest=Earliest -self.Latest=Latest -return self -end -function AI_BALANCER:ReturnToNearestAirbases(ReturnThresholdRange,ReturnAirbaseSet) -self.ToNearestAirbase=true -self.ReturnThresholdRange=ReturnThresholdRange -self.ReturnAirbaseSet=ReturnAirbaseSet -end -function AI_BALANCER:ReturnToHomeAirbase(ReturnThresholdRange) -self.ToHomeAirbase=true -self.ReturnThresholdRange=ReturnThresholdRange -end -function AI_BALANCER:onenterSpawning(SetGroup,From,Event,To,ClientName) -local AIGroup=self.SpawnAI:Spawn() -if AIGroup then -AIGroup:T({"Spawning new AIGroup",ClientName=ClientName}) -SetGroup:Remove(ClientName) -SetGroup:Add(ClientName,AIGroup) -self.SpawnQueue[ClientName]=nil -self:Spawned(AIGroup) -end -end -function AI_BALANCER:onenterDestroying(SetGroup,From,Event,To,ClientName,AIGroup) -AIGroup:Destroy() -SetGroup:Flush(self) -SetGroup:Remove(ClientName) -SetGroup:Flush(self) -end -function AI_BALANCER:onenterReturning(SetGroup,From,Event,To,AIGroup) -local AIGroupTemplate=AIGroup:GetTemplate() -if self.ToHomeAirbase==true then -local WayPointCount=#AIGroupTemplate.route.points -local SwitchWayPointCommand=AIGroup:CommandSwitchWayPoint(1,WayPointCount,1) -AIGroup:SetCommand(SwitchWayPointCommand) -AIGroup:MessageToRed("Returning to home base ...",30) -else -local PointVec2=POINT_VEC2:New(AIGroup:GetVec2().x,AIGroup:GetVec2().y) -local ClosestAirbase=self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2(PointVec2) -self:T(ClosestAirbase.AirbaseName) -AIGroup:RouteRTB(ClosestAirbase) -end -end -function AI_BALANCER:onenterMonitoring(SetGroup) -self:T2({self.SetClient:Count()}) -self.SetClient:ForEachClient( -function(Client) -self:T3(Client.ClientName) -local AIGroup=self.Set:Get(Client.UnitName) -if AIGroup then self:T({AIGroup=AIGroup:GetName(),IsAlive=AIGroup:IsAlive()})end -if Client:IsAlive()==true then -if AIGroup and AIGroup:IsAlive()==true then -if self.ToNearestAirbase==false and self.ToHomeAirbase==false then -self:Destroy(Client.UnitName,AIGroup) -else -local PlayerInRange={Value=false} -local RangeZone=ZONE_RADIUS:New('RangeZone',AIGroup:GetVec2(),self.ReturnThresholdRange) -self:T2(RangeZone) -_DATABASE:ForEachPlayerUnit( -function(RangeTestUnit,RangeZone,AIGroup,PlayerInRange) -self:T2({PlayerInRange,RangeTestUnit.UnitName,RangeZone.ZoneName}) -if RangeTestUnit:IsInZone(RangeZone)==true then -self:T2("in zone") -if RangeTestUnit:GetCoalition()~=AIGroup:GetCoalition()then -self:T2("in range") -PlayerInRange.Value=true -end -end -end, -function(RangeZone,AIGroup,PlayerInRange) -if PlayerInRange.Value==false then -self:Return(AIGroup) -end -end -,RangeZone,AIGroup,PlayerInRange -) -end -self.Set:Remove(Client.UnitName) -end -else -if not AIGroup or not AIGroup:IsAlive()==true then -self:T("Client "..Client.UnitName.." not alive.") -self:T({Queue=self.SpawnQueue[Client.UnitName]}) -if not self.SpawnQueue[Client.UnitName]then -self:__Spawn(math.random(self.Earliest,self.Latest),Client.UnitName) -self.SpawnQueue[Client.UnitName]=true -self:T("New AI Spawned for Client "..Client.UnitName) -end -end -end -return true -end -) -self:__Monitor(10) -end -AI_AIR={ -ClassName="AI_AIR", -} -AI_AIR.TaskDelay=0.5 -function AI_AIR:New(AIGroup) -local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) -self:SetControllable(AIGroup) -self:SetStartState("Stopped") -self:AddTransition("*","Queue","Queued") -self:AddTransition("*","Start","Started") -self:AddTransition("*","Stop","Stopped") -self:AddTransition("*","Status","*") -self:AddTransition("*","RTB","*") -self:AddTransition("Patrolling","Refuel","Refuelling") -self:AddTransition("*","Takeoff","Airborne") -self:AddTransition("*","Return","Returning") -self:AddTransition("*","Hold","Holding") -self:AddTransition("*","Home","Home") -self:AddTransition("*","LostControl","LostControl") -self:AddTransition("*","Fuel","Fuel") -self:AddTransition("*","Damaged","Damaged") -self:AddTransition("*","Eject","*") -self:AddTransition("*","Crash","Crashed") -self:AddTransition("*","PilotDead","*") -self.IdleCount=0 -self.RTBSpeedMaxFactor=0.6 -self.RTBSpeedMinFactor=0.5 -return self -end -function GROUP:OnEventTakeoff(EventData,Fsm) -Fsm:Takeoff() -self:UnHandleEvent(EVENTS.Takeoff) -end -function AI_AIR:SetDispatcher(Dispatcher) -self.Dispatcher=Dispatcher -end -function AI_AIR:GetDispatcher() -return self.Dispatcher -end -function AI_AIR:SetTargetDistance(Coordinate) -local CurrentCoord=self.Controllable:GetCoordinate() -self.TargetDistance=CurrentCoord:Get2DDistance(Coordinate) -self.ClosestTargetDistance=(not self.ClosestTargetDistance or self.ClosestTargetDistance>self.TargetDistance)and self.TargetDistance or self.ClosestTargetDistance -end -function AI_AIR:ClearTargetDistance() -self.TargetDistance=nil -self.ClosestTargetDistance=nil -end -function AI_AIR:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed) -self:F2({PatrolMinSpeed,PatrolMaxSpeed}) -self.PatrolMinSpeed=PatrolMinSpeed -self.PatrolMaxSpeed=PatrolMaxSpeed -end -function AI_AIR:SetRTBSpeed(RTBMinSpeed,RTBMaxSpeed) -self:F({RTBMinSpeed,RTBMaxSpeed}) -self.RTBMinSpeed=RTBMinSpeed -self.RTBMaxSpeed=RTBMaxSpeed -end -function AI_AIR:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude) -self:F2({PatrolFloorAltitude,PatrolCeilingAltitude}) -self.PatrolFloorAltitude=PatrolFloorAltitude -self.PatrolCeilingAltitude=PatrolCeilingAltitude -end -function AI_AIR:SetHomeAirbase(HomeAirbase) -self:F2({HomeAirbase}) -self.HomeAirbase=HomeAirbase -end -function AI_AIR:SetTanker(TankerName) -self:F2({TankerName}) -self.TankerName=TankerName -end -function AI_AIR:SetDisengageRadius(DisengageRadius) -self:F2({DisengageRadius}) -self.DisengageRadius=DisengageRadius -end -function AI_AIR:SetStatusOff() -self:F2() -self.CheckStatus=false -end -function AI_AIR:SetFuelThreshold(FuelThresholdPercentage,OutOfFuelOrbitTime) -self.FuelThresholdPercentage=FuelThresholdPercentage -self.OutOfFuelOrbitTime=OutOfFuelOrbitTime -self.Controllable:OptionRTBBingoFuel(false) -return self -end -function AI_AIR:SetDamageThreshold(PatrolDamageThreshold) -self.PatrolManageDamage=true -self.PatrolDamageThreshold=PatrolDamageThreshold -return self -end -function AI_AIR:onafterStart(Controllable,From,Event,To) -self:__Status(10) -self:HandleEvent(EVENTS.PilotDead,self.OnPilotDead) -self:HandleEvent(EVENTS.Crash,self.OnCrash) -self:HandleEvent(EVENTS.Ejection,self.OnEjection) -Controllable:OptionROEHoldFire() -Controllable:OptionROTVertical() -end -function AI_AIR:onafterReturn(Controllable,From,Event,To) -self:__RTB(self.TaskDelay) -end -function AI_AIR:onbeforeStatus() -return self.CheckStatus -end -function AI_AIR:onafterStatus() -if self.Controllable and self.Controllable:IsAlive()then -local RTB=false -local DistanceFromHomeBase=self.HomeAirbase:GetCoordinate():Get2DDistance(self.Controllable:GetCoordinate()) -if not self:Is("Holding")and not self:Is("Returning")then -local DistanceFromHomeBase=self.HomeAirbase:GetCoordinate():Get2DDistance(self.Controllable:GetCoordinate()) -if DistanceFromHomeBase>self.DisengageRadius then -self:I(self.Controllable:GetName().." is too far from home base, RTB!") -self:Hold(300) -RTB=false -end -end -if not self:Is("Fuel")and not self:Is("Home")and not self:is("Refuelling")then -local Fuel=self.Controllable:GetFuelMin() -if Fuel=10 then -if Damage~=InitialLife then -self:Damaged() -else -self:I(self.Controllable:GetName().." control lost! ") -self:LostControl() -end -else -self.IdleCount=self.IdleCount+1 -end -end -else -self.IdleCount=0 -end -if RTB==true then -self:__RTB(self.TaskDelay) -end -if not self:Is("Home")then -self:__Status(10) -end -end -end -function AI_AIR.RTBRoute(AIGroup,Fsm) -AIGroup:F({"AI_AIR.RTBRoute:",AIGroup:GetName()}) -if AIGroup:IsAlive()then -Fsm:RTB() -end -end -function AI_AIR.RTBHold(AIGroup,Fsm) -AIGroup:F({"AI_AIR.RTBHold:",AIGroup:GetName()}) -if AIGroup:IsAlive()then -Fsm:__RTB(Fsm.TaskDelay) -Fsm:Return() -local Task=AIGroup:TaskOrbitCircle(4000,400) -AIGroup:SetTask(Task) -end -end -function AI_AIR:SetRTBSpeedFactors(MinFactor,MaxFactor) -self.RTBSpeedMaxFactor=MaxFactor or 0.6 -self.RTBSpeedMinFactor=MinFactor or 0.5 -return self -end -function AI_AIR:onafterRTB(AIGroup,From,Event,To) -self:F({AIGroup,From,Event,To}) -if AIGroup and AIGroup:IsAlive()then -self:T("Group "..AIGroup:GetName().." ... RTB! ( "..self:GetState().." )") -self:ClearTargetDistance() -AIGroup:OptionProhibitAfterburner(true) -local EngageRoute={} -local FromCoord=AIGroup:GetCoordinate() -local ToTargetCoord=self.HomeAirbase:GetCoordinate() -local ToTargetVec3=ToTargetCoord:GetVec3() -ToTargetVec3.y=ToTargetCoord:GetLandHeight()+3000 -local ToTargetCoord2=COORDINATE:NewFromVec3(ToTargetVec3) -if not self.RTBMinSpeed or not self.RTBMaxSpeed then -local RTBSpeedMax=AIGroup:GetSpeedMax() -local RTBSpeedMaxFactor=self.RTBSpeedMaxFactor or 0.6 -local RTBSpeedMinFactor=self.RTBSpeedMinFactor or 0.5 -self:SetRTBSpeed(RTBSpeedMax*RTBSpeedMinFactor,RTBSpeedMax*RTBSpeedMaxFactor) -end -local RTBSpeed=math.random(self.RTBMinSpeed,self.RTBMaxSpeed) -local Distance=FromCoord:Get2DDistance(ToTargetCoord2) -local ToAirbaseCoord=ToTargetCoord2 -if Distance<5000 then -self:I("RTB and near the airbase!") -self:Home() -return -end -if not AIGroup:InAir()==true then -self:I("Not anymore in the air, considered Home.") -self:Home() -return -end -local FromRTBRoutePoint=FromCoord:WaypointAir( -self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, -RTBSpeed, -true -) -local ToRTBRoutePoint=ToAirbaseCoord:WaypointAir( -self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, -RTBSpeed, -true -) -EngageRoute[#EngageRoute+1]=FromRTBRoutePoint -EngageRoute[#EngageRoute+1]=ToRTBRoutePoint -local Tasks={} -Tasks[#Tasks+1]=AIGroup:TaskFunction("AI_AIR.RTBRoute",self) -EngageRoute[#EngageRoute].task=AIGroup:TaskCombo(Tasks) -AIGroup:OptionROEHoldFire() -AIGroup:OptionROTEvadeFire() -AIGroup:Route(EngageRoute,self.TaskDelay) -end -end -function AI_AIR:onafterHome(AIGroup,From,Event,To) -self:F({AIGroup,From,Event,To}) -self:I("Group "..self.Controllable:GetName().." ... Home! ( "..self:GetState().." )") -if AIGroup and AIGroup:IsAlive()then -end -end -function AI_AIR:onafterHold(AIGroup,From,Event,To,HoldTime) -self:F({AIGroup,From,Event,To}) -self:I("Group "..self.Controllable:GetName().." ... Holding! ( "..self:GetState().." )") -if AIGroup and AIGroup:IsAlive()then -local OrbitTask=AIGroup:TaskOrbitCircle(math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude),self.PatrolMinSpeed) -local TimedOrbitTask=AIGroup:TaskControlled(OrbitTask,AIGroup:TaskCondition(nil,nil,nil,nil,HoldTime,nil)) -local RTBTask=AIGroup:TaskFunction("AI_AIR.RTBHold",self) -local OrbitHoldTask=AIGroup:TaskOrbitCircle(4000,self.PatrolMinSpeed) -AIGroup:SetTask(AIGroup:TaskCombo({TimedOrbitTask,RTBTask,OrbitHoldTask}),1) -end -end -function AI_AIR.Resume(AIGroup,Fsm) -AIGroup:I({"AI_AIR.Resume:",AIGroup:GetName()}) -if AIGroup:IsAlive()then -Fsm:__RTB(Fsm.TaskDelay) -end -end -function AI_AIR:onafterRefuel(AIGroup,From,Event,To) -self:F({AIGroup,From,Event,To}) -if AIGroup and AIGroup:IsAlive()then -local Tanker=GROUP:FindByName(self.TankerName) -if Tanker and Tanker:IsAlive()and Tanker:IsAirPlane()then -self:I("Group "..self.Controllable:GetName().." ... Refuelling! State="..self:GetState()..", Refuelling tanker "..self.TankerName) -local RefuelRoute={} -local FromRefuelCoord=AIGroup:GetCoordinate() -local ToRefuelCoord=Tanker:GetCoordinate() -local ToRefuelSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) -local FromRefuelRoutePoint=FromRefuelCoord:WaypointAir(self.PatrolAltType,POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToRefuelSpeed,true) -local ToRefuelRoutePoint=Tanker:GetCoordinate():WaypointAir(self.PatrolAltType,POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToRefuelSpeed,true) -self:F({ToRefuelSpeed=ToRefuelSpeed}) -RefuelRoute[#RefuelRoute+1]=FromRefuelRoutePoint -RefuelRoute[#RefuelRoute+1]=ToRefuelRoutePoint -AIGroup:OptionROEHoldFire() -AIGroup:OptionROTEvadeFire() -local classname=self:GetClassName() -if classname=="AI_A2A_CAP"then -classname="AI_AIR_PATROL" -end -env.info("FF refueling classname="..classname) -local Tasks={} -Tasks[#Tasks+1]=AIGroup:TaskRefueling() -Tasks[#Tasks+1]=AIGroup:TaskFunction(classname..".Resume",self) -RefuelRoute[#RefuelRoute].task=AIGroup:TaskCombo(Tasks) -AIGroup:Route(RefuelRoute,self.TaskDelay) -else -self:RTB() -end -end -end -function AI_AIR:onafterDead() -self:SetStatusOff() -end -function AI_AIR:OnCrash(EventData) -if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then -if#self.Controllable:GetUnits()==1 then -self:__Crash(self.TaskDelay,EventData) -end -end -end -function AI_AIR:OnEjection(EventData) -if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then -self:__Eject(self.TaskDelay,EventData) -end -end -function AI_AIR:OnPilotDead(EventData) -if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then -self:__PilotDead(self.TaskDelay,EventData) -end -end -AI_AIR_PATROL={ -ClassName="AI_AIR_PATROL", -} -function AI_AIR_PATROL:New(AI_Air,AIGroup,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) -local self=BASE:Inherit(self,AI_Air) -local SpeedMax=AIGroup:GetSpeedMax() -self.PatrolZone=PatrolZone -self.PatrolFloorAltitude=PatrolFloorAltitude or 1000 -self.PatrolCeilingAltitude=PatrolCeilingAltitude or 1500 -self.PatrolMinSpeed=PatrolMinSpeed or SpeedMax*0.5 -self.PatrolMaxSpeed=PatrolMaxSpeed or SpeedMax*0.75 -self.PatrolAltType=PatrolAltType or"RADIO" -self:AddTransition({"Started","Airborne","Refuelling"},"Patrol","Patrolling") -self:AddTransition("Patrolling","PatrolRoute","Patrolling") -self:AddTransition("*","Reset","Patrolling") -return self -end -function AI_AIR_PATROL:SetEngageRange(EngageRange) -self:F2() -if EngageRange then -self.EngageRange=EngageRange -else -self.EngageRange=nil -end -end -function AI_AIR_PATROL:SetRaceTrackPattern(LegMin,LegMax,HeadingMin,HeadingMax,DurationMin,DurationMax,CapCoordinates) -self.racetrack=true -self.racetracklegmin=LegMin or 10000 -self.racetracklegmax=LegMax or 15000 -self.racetrackheadingmin=HeadingMin or 0 -self.racetrackheadingmax=HeadingMax or 180 -self.racetrackdurationmin=DurationMin -self.racetrackdurationmax=DurationMax -if self.racetrackdurationmax and not self.racetrackdurationmin then -self.racetrackdurationmin=self.racetrackdurationmax -end -self.racetrackcapcoordinates=CapCoordinates -end -function AI_AIR_PATROL:onafterPatrol(AIPatrol,From,Event,To) -self:F2() -self:ClearTargetDistance() -self:__PatrolRoute(self.TaskDelay) -AIPatrol:OnReSpawn( -function(PatrolGroup) -self:__Reset(self.TaskDelay) -self:__PatrolRoute(self.TaskDelay) -end -) -end -function AI_AIR_PATROL.___PatrolRoute(AIPatrol,Fsm) -AIPatrol:F({"AI_AIR_PATROL.___PatrolRoute:",AIPatrol:GetName()}) -if AIPatrol and AIPatrol:IsAlive()then -Fsm:PatrolRoute() -end -end -function AI_AIR_PATROL:onafterPatrolRoute(AIPatrol,From,Event,To) -self:F2() -if From=="RTB"then -return -end -if AIPatrol and AIPatrol:IsAlive()then -local PatrolRoute={} -local CurrentCoord=AIPatrol:GetCoordinate() -local altitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude) -local ToTargetCoord=self.PatrolZone:GetRandomPointVec2() -ToTargetCoord:SetAlt(altitude) -self:SetTargetDistance(ToTargetCoord) -local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) -local speedkmh=ToTargetSpeed -local FromWP=CurrentCoord:WaypointAir(self.PatrolAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToTargetSpeed,true) -PatrolRoute[#PatrolRoute+1]=FromWP -if self.racetrack then -local heading=math.random(self.racetrackheadingmin,self.racetrackheadingmax) -local leg=math.random(self.racetracklegmin,self.racetracklegmax) -local duration=self.racetrackdurationmin -if self.racetrackdurationmax then -duration=math.random(self.racetrackdurationmin,self.racetrackdurationmax) -end -local c0=self.PatrolZone:GetRandomCoordinate() -if self.racetrackcapcoordinates and#self.racetrackcapcoordinates>0 then -c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)] -end -local c1=c0:SetAltitude(altitude) -local c2=c1:Translate(leg,heading):SetAltitude(altitude) -self:SetTargetDistance(c0) -self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec",UTILS.KmphToKnots(speedkmh),UTILS.MetersToFeet(altitude),heading,leg,tostring(duration))) -local taskOrbit=AIPatrol:TaskOrbit(c1,altitude,UTILS.KmphToMps(speedkmh),c2) -local taskPatrol=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute",self) -local taskCond=AIPatrol:TaskCondition(nil,nil,nil,nil,duration,nil) -local taskCont=AIPatrol:TaskControlled(taskOrbit,taskCond) -PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskCont,taskPatrol},"CAP Orbit") -else -local ToWP=ToTargetCoord:WaypointAir(self.PatrolAltType,POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToTargetSpeed,true) -PatrolRoute[#PatrolRoute+1]=ToWP -local Tasks={} -Tasks[#Tasks+1]=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute",self) -PatrolRoute[#PatrolRoute].task=AIPatrol:TaskCombo(Tasks) -end -AIPatrol:OptionROEReturnFire() -AIPatrol:OptionROTEvadeFire() -AIPatrol:Route(PatrolRoute,self.TaskDelay) -end -end -function AI_AIR_PATROL.Resume(AIPatrol,Fsm) -AIPatrol:F({"AI_AIR_PATROL.Resume:",AIPatrol:GetName()}) -if AIPatrol and AIPatrol:IsAlive()then -Fsm:__Reset(Fsm.TaskDelay) -Fsm:__PatrolRoute(Fsm.TaskDelay) -end -end -AI_AIR_ENGAGE={ -ClassName="AI_AIR_ENGAGE", -} -function AI_AIR_ENGAGE:New(AI_Air,AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -local self=BASE:Inherit(self,AI_Air) -self.Accomplished=false -self.Engaging=false -local SpeedMax=AIGroup:GetSpeedMax() -self.EngageMinSpeed=EngageMinSpeed or SpeedMax*0.5 -self.EngageMaxSpeed=EngageMaxSpeed or SpeedMax*0.75 -self.EngageFloorAltitude=EngageFloorAltitude or 1000 -self.EngageCeilingAltitude=EngageCeilingAltitude or 1500 -self.EngageAltType=EngageAltType or"RADIO" -self:AddTransition({"Started","Engaging","Returning","Airborne","Patrolling"},"EngageRoute","Engaging") -self:AddTransition({"Started","Engaging","Returning","Airborne","Patrolling"},"Engage","Engaging") -self:AddTransition("Engaging","Fired","Engaging") -self:AddTransition("*","Destroy","*") -self:AddTransition("Engaging","Abort","Patrolling") -self:AddTransition("Engaging","Accomplish","Patrolling") -self:AddTransition({"Patrolling","Engaging"},"Refuel","Refuelling") -return self -end -function AI_AIR_ENGAGE:onafterStart(AIGroup,From,Event,To) -self:GetParent(self,AI_AIR_ENGAGE).onafterStart(self,AIGroup,From,Event,To) -AIGroup:HandleEvent(EVENTS.Takeoff,nil,self) -end -function AI_AIR_ENGAGE:onafterEngage(AIGroup,From,Event,To) -self:HandleEvent(EVENTS.Dead) -end -function AI_AIR_ENGAGE:onbeforeEngage(AIGroup,From,Event,To) -if self.Accomplished==true then -return false -end -return true -end -function AI_AIR_ENGAGE:onafterAbort(AIGroup,From,Event,To) -AIGroup:ClearTasks() -self:Return() -end -function AI_AIR_ENGAGE:onafterAccomplish(AIGroup,From,Event,To) -self.Accomplished=true -end -function AI_AIR_ENGAGE:onafterDestroy(AIGroup,From,Event,To,EventData) -if EventData.IniUnit then -self.AttackUnits[EventData.IniUnit]=nil -end -end -function AI_AIR_ENGAGE:OnEventDead(EventData) -self:F({"EventDead",EventData}) -if EventData.IniDCSUnit then -if self.AttackUnits and self.AttackUnits[EventData.IniUnit]then -self:__Destroy(self.TaskDelay,EventData) -end -end -end -function AI_AIR_ENGAGE.___EngageRoute(AIGroup,Fsm,AttackSetUnit) -Fsm:I(string.format("AI_AIR_ENGAGE.___EngageRoute: %s",tostring(AIGroup:GetName()))) -if AIGroup and AIGroup:IsAlive()then -Fsm:__EngageRoute(Fsm.TaskDelay or 0.1,AttackSetUnit) -end -end -function AI_AIR_ENGAGE:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit) -self:I({DefenderGroup,From,Event,To,AttackSetUnit}) -local DefenderGroupName=DefenderGroup:GetName() -self.AttackSetUnit=AttackSetUnit -local AttackCount=AttackSetUnit:CountAlive() -if AttackCount>0 then -if DefenderGroup:IsAlive()then -local EngageAltitude=math.random(self.EngageFloorAltitude,self.EngageCeilingAltitude) -local EngageSpeed=math.random(self.EngageMinSpeed,self.EngageMaxSpeed) -local DefenderCoord=DefenderGroup:GetPointVec3() -DefenderCoord:SetY(EngageAltitude) -local TargetCoord=AttackSetUnit:GetFirst():GetPointVec3() -TargetCoord:SetY(EngageAltitude) -local TargetDistance=DefenderCoord:Get2DDistance(TargetCoord) -local EngageDistance=(DefenderGroup:IsHelicopter()and 5000)or(DefenderGroup:IsAirPlane()and 10000) -if TargetDistance<=EngageDistance*9 then -self:__Engage(0.1,AttackSetUnit) -else -local EngageRoute={} -local AttackTasks={} -local FromWP=DefenderCoord:WaypointAir(self.PatrolAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true) -EngageRoute[#EngageRoute+1]=FromWP -self:SetTargetDistance(TargetCoord) -local FromEngageAngle=DefenderCoord:GetAngleDegrees(DefenderCoord:GetDirectionVec3(TargetCoord)) -local ToCoord=DefenderCoord:Translate(EngageDistance,FromEngageAngle,true) -local ToWP=ToCoord:WaypointAir(self.PatrolAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true) -EngageRoute[#EngageRoute+1]=ToWP -AttackTasks[#AttackTasks+1]=DefenderGroup:TaskFunction("AI_AIR_ENGAGE.___EngageRoute",self,AttackSetUnit) -EngageRoute[#EngageRoute].task=DefenderGroup:TaskCombo(AttackTasks) -DefenderGroup:OptionROEReturnFire() -DefenderGroup:OptionROTEvadeFire() -DefenderGroup:Route(EngageRoute,self.TaskDelay or 0.1) -end -end -else -self:I(DefenderGroupName..": No targets found -> Going RTB") -self:Return() -end -end -function AI_AIR_ENGAGE.___Engage(AIGroup,Fsm,AttackSetUnit) -Fsm:I(string.format("AI_AIR_ENGAGE.___Engage: %s",tostring(AIGroup:GetName()))) -if AIGroup and AIGroup:IsAlive()then -local delay=Fsm.TaskDelay or 0.1 -Fsm:__Engage(delay,AttackSetUnit) -end -end -function AI_AIR_ENGAGE:onafterEngage(DefenderGroup,From,Event,To,AttackSetUnit) -self:F({DefenderGroup,From,Event,To,AttackSetUnit}) -local DefenderGroupName=DefenderGroup:GetName() -self.AttackSetUnit=AttackSetUnit -local AttackCount=AttackSetUnit:CountAlive() -self:T({AttackCount=AttackCount}) -if AttackCount>0 then -if DefenderGroup and DefenderGroup:IsAlive()then -local EngageAltitude=math.random(self.EngageFloorAltitude or 500,self.EngageCeilingAltitude or 1000) -local EngageSpeed=math.random(self.EngageMinSpeed,self.EngageMaxSpeed) -local DefenderCoord=DefenderGroup:GetPointVec3() -DefenderCoord:SetY(EngageAltitude) -local TargetCoord=AttackSetUnit:GetFirst():GetPointVec3() -if not TargetCoord then -self:Return() -return -end -TargetCoord:SetY(EngageAltitude) -local TargetDistance=DefenderCoord:Get2DDistance(TargetCoord) -local EngageDistance=(DefenderGroup:IsHelicopter()and 5000)or(DefenderGroup:IsAirPlane()and 10000) -local EngageRoute={} -local AttackTasks={} -local FromWP=DefenderCoord:WaypointAir(self.EngageAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true) -EngageRoute[#EngageRoute+1]=FromWP -self:SetTargetDistance(TargetCoord) -local FromEngageAngle=DefenderCoord:GetAngleDegrees(DefenderCoord:GetDirectionVec3(TargetCoord)) -local ToCoord=DefenderCoord:Translate(EngageDistance,FromEngageAngle,true) -local ToWP=ToCoord:WaypointAir(self.EngageAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true) -EngageRoute[#EngageRoute+1]=ToWP -if TargetDistance<=EngageDistance*9 then -local AttackUnitTasks=self:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude) -if#AttackUnitTasks==0 then -self:I(DefenderGroupName..": No valid targets found -> Going RTB") -self:Return() -return -else -local text=string.format("%s: Engaging targets at distance %.2f NM",DefenderGroupName,UTILS.MetersToNM(TargetDistance)) -self:I(text) -DefenderGroup:OptionROEOpenFire() -DefenderGroup:OptionROTEvadeFire() -DefenderGroup:OptionKeepWeaponsOnThreat() -AttackTasks[#AttackTasks+1]=DefenderGroup:TaskCombo(AttackUnitTasks) -end -end -AttackTasks[#AttackTasks+1]=DefenderGroup:TaskFunction("AI_AIR_ENGAGE.___Engage",self,AttackSetUnit) -EngageRoute[#EngageRoute].task=DefenderGroup:TaskCombo(AttackTasks) -DefenderGroup:Route(EngageRoute,self.TaskDelay or 0.1) -end -else -self:I(DefenderGroupName..": No targets found -> returning.") -self:Return() -return -end -end -function AI_AIR_ENGAGE.Resume(AIEngage,Fsm) -AIEngage:F({"Resume:",AIEngage:GetName()}) -if AIEngage and AIEngage:IsAlive()then -Fsm:__Reset(Fsm.TaskDelay or 0.1) -Fsm:__EngageRoute(Fsm.TaskDelay or 0.2,Fsm.AttackSetUnit) -end -end -AI_A2A_PATROL={ -ClassName="AI_A2A_PATROL", -} -function AI_A2A_PATROL:New(AIPatrol,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) -local AI_Air=AI_AIR:New(AIPatrol) -local AI_Air_Patrol=AI_AIR_PATROL:New(AI_Air,AIPatrol,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) -local self=BASE:Inherit(self,AI_Air_Patrol) -self:SetFuelThreshold(.2,60) -self:SetDamageThreshold(0.4) -self:SetDisengageRadius(70000) -self.PatrolZone=PatrolZone -self.PatrolFloorAltitude=PatrolFloorAltitude -self.PatrolCeilingAltitude=PatrolCeilingAltitude -self.PatrolMinSpeed=PatrolMinSpeed -self.PatrolMaxSpeed=PatrolMaxSpeed -self.PatrolAltType=PatrolAltType or"BARO" -self:AddTransition({"Started","Airborne","Refuelling"},"Patrol","Patrolling") -self:AddTransition("Patrolling","Route","Patrolling") -self:AddTransition("*","Reset","Patrolling") -return self -end -function AI_A2A_PATROL:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed) -self:F2({PatrolMinSpeed,PatrolMaxSpeed}) -self.PatrolMinSpeed=PatrolMinSpeed -self.PatrolMaxSpeed=PatrolMaxSpeed -end -function AI_A2A_PATROL:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude) -self:F2({PatrolFloorAltitude,PatrolCeilingAltitude}) -self.PatrolFloorAltitude=PatrolFloorAltitude -self.PatrolCeilingAltitude=PatrolCeilingAltitude -end -function AI_A2A_PATROL:onafterPatrol(AIPatrol,From,Event,To) -self:F2() -self:ClearTargetDistance() -self:__Route(1) -AIPatrol:OnReSpawn( -function(PatrolGroup) -self:__Reset(1) -self:__Route(5) -end -) -end -function AI_A2A_PATROL.PatrolRoute(AIPatrol,Fsm) -AIPatrol:F({"AI_A2A_PATROL.PatrolRoute:",AIPatrol:GetName()}) -if AIPatrol and AIPatrol:IsAlive()then -Fsm:Route() -end -end -function AI_A2A_PATROL:onafterRoute(AIPatrol,From,Event,To) -self:F2() -if From=="RTB"then -return -end -if AIPatrol and AIPatrol:IsAlive()then -local PatrolRoute={} -local CurrentCoord=AIPatrol:GetCoordinate() -local altitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude) -local speedkmh=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) -PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil,speedkmh,{},"Current") -if self.racetrack then -local heading=math.random(self.racetrackheadingmin,self.racetrackheadingmax) -local leg=math.random(self.racetracklegmin,self.racetracklegmax) -local duration=self.racetrackdurationmin -if self.racetrackdurationmax then -duration=math.random(self.racetrackdurationmin,self.racetrackdurationmax) -end -local c0=self.PatrolZone:GetRandomCoordinate() -if self.racetrackcapcoordinates and#self.racetrackcapcoordinates>0 then -c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)] -end -local c1=c0:SetAltitude(altitude) -local c2=c1:Translate(leg,heading):SetAltitude(altitude) -self:SetTargetDistance(c0) -self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec",UTILS.KmphToKnots(speedkmh),UTILS.MetersToFeet(altitude),heading,leg,tostring(duration))) -local taskOrbit=AIPatrol:TaskOrbit(c1,altitude,UTILS.KmphToMps(speedkmh),c2) -local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute",self) -local taskCond=AIPatrol:TaskCondition(nil,nil,nil,nil,duration,nil) -local taskCont=AIPatrol:TaskControlled(taskOrbit,taskCond) -PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskCont,taskPatrol},"CAP Orbit") -else -local ToTargetCoord=self.PatrolZone:GetRandomCoordinate() -ToTargetCoord:SetAltitude(altitude) -self:SetTargetDistance(ToTargetCoord) -local taskReRoute=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute",self) -PatrolRoute[2]=ToTargetCoord:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskReRoute},"Patrol Point") -end -AIPatrol:OptionROEReturnFire() -AIPatrol:OptionROTEvadeFire() -AIPatrol:Route(PatrolRoute,0.5) -end -end -AI_A2A_CAP={ -ClassName="AI_A2A_CAP", -} -function AI_A2A_CAP:New2(AICap,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType,PatrolZone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType) -local AI_Air=AI_AIR:New(AICap) -local AI_Air_Patrol=AI_AIR_PATROL:New(AI_Air,AICap,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) -local AI_Air_Engage=AI_AIR_ENGAGE:New(AI_Air_Patrol,AICap,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -local self=BASE:Inherit(self,AI_Air_Engage) -self:SetFuelThreshold(.2,60) -self:SetDamageThreshold(0.4) -self:SetDisengageRadius(70000) -return self -end -function AI_A2A_CAP:New(AICap,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,PatrolAltType) -return self:New2(AICap,EngageMinSpeed,EngageMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,PatrolZone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType) -end -function AI_A2A_CAP:onafterStart(AICap,From,Event,To) -self:GetParent(self,AI_A2A_CAP).onafterStart(self,AICap,From,Event,To) -AICap:HandleEvent(EVENTS.Takeoff,nil,self) -end -function AI_A2A_CAP:SetEngageZone(EngageZone) -self:F2() -if EngageZone then -self.EngageZone=EngageZone -else -self.EngageZone=nil -end -end -function AI_A2A_CAP:SetEngageRange(EngageRange) -self:F2() -if EngageRange then -self.EngageRange=EngageRange -else -self.EngageRange=nil -end -end -function AI_A2A_CAP:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude) -local AttackUnitTasks={} -for AttackUnitID,AttackUnit in pairs(self.AttackSetUnit:GetSet())do -local AttackUnit=AttackUnit -if AttackUnit and AttackUnit:IsAlive()and AttackUnit:IsAir()then -self:T({"Attacking Task:",AttackUnit:GetName(),AttackUnit:IsAlive(),AttackUnit:IsAir()}) -AttackUnitTasks[#AttackUnitTasks+1]=DefenderGroup:TaskAttackUnit(AttackUnit) -end -end -return AttackUnitTasks -end -AI_A2A_GCI={ -ClassName="AI_A2A_GCI", -} -function AI_A2A_GCI:New2(AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -local AI_Air=AI_AIR:New(AIIntercept) -local AI_Air_Engage=AI_AIR_ENGAGE:New(AI_Air,AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -local self=BASE:Inherit(self,AI_Air_Engage) -self:SetFuelThreshold(.2,60) -self:SetDamageThreshold(0.4) -self:SetDisengageRadius(70000) -return self -end -function AI_A2A_GCI:New(AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -return self:New2(AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -end -function AI_A2A_GCI:onafterStart(AIIntercept,From,Event,To) -self:GetParent(self,AI_A2A_GCI).onafterStart(self,AIIntercept,From,Event,To) -end -function AI_A2A_GCI:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude) -local AttackUnitTasks={} -for AttackUnitID,AttackUnit in pairs(self.AttackSetUnit:GetSet())do -local AttackUnit=AttackUnit -self:T({"Attacking Unit:",AttackUnit:GetName(),AttackUnit:IsAlive(),AttackUnit:IsAir()}) -if AttackUnit:IsAlive()and AttackUnit:IsAir()then -AttackUnitTasks[#AttackUnitTasks+1]=DefenderGroup:TaskAttackUnit(AttackUnit) -end -end -return AttackUnitTasks -end -do -AI_A2A_DISPATCHER={ -ClassName="AI_A2A_DISPATCHER", -Detection=nil, -} -AI_A2A_DISPATCHER.Takeoff=GROUP.Takeoff -AI_A2A_DISPATCHER.Landing={ -NearAirbase=1, -AtRunway=2, -AtEngineShutdown=3, -} -function AI_A2A_DISPATCHER:New(Detection) -local self=BASE:Inherit(self,DETECTION_MANAGER:New(nil,Detection)) -self.Detection=Detection -self.DefenderSquadrons={} -self.DefenderSpawns={} -self.DefenderTasks={} -self.DefenderDefault={} -self.SetSendPlayerMessages=false -self.Detection:FilterCategories({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) -self.Detection:SetRefreshTimeInterval(30) -self:SetEngageRadius() -self:SetGciRadius() -self:SetIntercept(300) -self:SetDisengageRadius(300000) -self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Air) -self:SetDefaultTakeoffInAirAltitude(500) -self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.NearAirbase) -self:SetDefaultOverhead(1) -self:SetDefaultGrouping(1) -self:SetDefaultFuelThreshold(0.15,0) -self:SetDefaultDamageThreshold(0.4) -self:SetDefaultCapTimeInterval(180,600) -self:SetDefaultCapLimit(1) -self:AddTransition("Started","Assign","Started") -self:AddTransition("*","CAP","*") -self:AddTransition("*","GCI","*") -self:AddTransition("*","ENGAGE","*") -self:HandleEvent(EVENTS.Crash,self.OnEventCrashOrDead) -self:HandleEvent(EVENTS.Dead,self.OnEventCrashOrDead) -self:HandleEvent(EVENTS.Land) -self:HandleEvent(EVENTS.EngineShutdown) -self:HandleEvent(EVENTS.BaseCaptured) -self:SetTacticalDisplay(false) -self.DefenderCAPIndex=0 -self:__Start(5) -return self -end -function AI_A2A_DISPATCHER:onafterStart(From,Event,To) -self:GetParent(self,AI_A2A_DISPATCHER).onafterStart(self,From,Event,To) -for SquadronName,_DefenderSquadron in pairs(self.DefenderSquadrons)do -local DefenderSquadron=_DefenderSquadron -DefenderSquadron.Resources={} -if DefenderSquadron.ResourceCount then -for Resource=1,DefenderSquadron.ResourceCount do -self:ParkDefender(DefenderSquadron) -end -end -end -end -function AI_A2A_DISPATCHER:ParkDefender(DefenderSquadron) -local TemplateID=math.random(1,#DefenderSquadron.Spawn) -local Spawn=DefenderSquadron.Spawn[TemplateID] -Spawn:InitGrouping(1) -local SpawnGroup -if self:IsSquadronVisible(DefenderSquadron.Name)then -local Grouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping -Grouping=1 -Spawn:InitGrouping(Grouping) -SpawnGroup=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,SPAWN.Takeoff.Cold) -local GroupName=SpawnGroup:GetName() -DefenderSquadron.Resources=DefenderSquadron.Resources or{} -DefenderSquadron.Resources[TemplateID]=DefenderSquadron.Resources[TemplateID]or{} -DefenderSquadron.Resources[TemplateID][GroupName]={} -DefenderSquadron.Resources[TemplateID][GroupName]=SpawnGroup -self.uncontrolled=self.uncontrolled or{} -self.uncontrolled[DefenderSquadron.Name]=self.uncontrolled[DefenderSquadron.Name]or{} -table.insert(self.uncontrolled[DefenderSquadron.Name],{group=SpawnGroup,name=GroupName,grouping=Grouping}) -end -end -function AI_A2A_DISPATCHER:OnEventBaseCaptured(EventData) -local AirbaseName=EventData.PlaceName -self:I("Captured "..AirbaseName) -for SquadronName,Squadron in pairs(self.DefenderSquadrons)do -if Squadron.AirbaseName==AirbaseName then -Squadron.ResourceCount=-999 -Squadron.Captured=true -self:I("Squadron "..SquadronName.." captured.") -end -end -end -function AI_A2A_DISPATCHER:OnEventCrashOrDead(EventData) -self.Detection:ForgetDetectedUnit(EventData.IniUnitName) -end -function AI_A2A_DISPATCHER:OnEventLand(EventData) -self:F("Landed") -local DefenderUnit=EventData.IniUnit -local Defender=EventData.IniGroup -local Squadron=self:GetSquadronFromDefender(Defender) -if Squadron then -self:F({SquadronName=Squadron.Name}) -local LandingMethod=self:GetSquadronLanding(Squadron.Name) -if LandingMethod==AI_A2A_DISPATCHER.Landing.AtRunway then -local DefenderSize=Defender:GetSize() -if DefenderSize==1 then -self:RemoveDefenderFromSquadron(Squadron,Defender) -end -DefenderUnit:Destroy() -self:ParkDefender(Squadron) -return -end -if DefenderUnit:GetLife()~=DefenderUnit:GetLife0()then -DefenderUnit:Destroy() -return -end -end -end -function AI_A2A_DISPATCHER:OnEventEngineShutdown(EventData) -local DefenderUnit=EventData.IniUnit -local Defender=EventData.IniGroup -local Squadron=self:GetSquadronFromDefender(Defender) -if Squadron then -self:F({SquadronName=Squadron.Name}) -local LandingMethod=self:GetSquadronLanding(Squadron.Name) -if LandingMethod==AI_A2A_DISPATCHER.Landing.AtEngineShutdown and not DefenderUnit:InAir()then -local DefenderSize=Defender:GetSize() -if DefenderSize==1 then -self:RemoveDefenderFromSquadron(Squadron,Defender) -end -DefenderUnit:Destroy() -self:ParkDefender(Squadron) -end -end -end -function AI_A2A_DISPATCHER:SetEngageRadius(EngageRadius) -self.Detection:SetFriendliesRange(EngageRadius or 100000) -return self -end -function AI_A2A_DISPATCHER:SetDisengageRadius(DisengageRadius) -self.DisengageRadius=DisengageRadius or 300000 -return self -end -function AI_A2A_DISPATCHER:SetGciRadius(GciRadius) -self.GciRadius=GciRadius or 200000 -return self -end -function AI_A2A_DISPATCHER:SetBorderZone(BorderZone) -self.Detection:SetAcceptZones(BorderZone) -return self -end -function AI_A2A_DISPATCHER:SetTacticalDisplay(TacticalDisplay) -self.TacticalDisplay=TacticalDisplay -return self -end -function AI_A2A_DISPATCHER:SetDefaultDamageThreshold(DamageThreshold) -self.DefenderDefault.DamageThreshold=DamageThreshold -return self -end -function AI_A2A_DISPATCHER:SetDefaultCapTimeInterval(CapMinSeconds,CapMaxSeconds) -self.DefenderDefault.CapMinSeconds=CapMinSeconds -self.DefenderDefault.CapMaxSeconds=CapMaxSeconds -return self -end -function AI_A2A_DISPATCHER:SetDefaultCapLimit(CapLimit) -self.DefenderDefault.CapLimit=CapLimit -return self -end -function AI_A2A_DISPATCHER:SetIntercept(InterceptDelay) -self.DefenderDefault.InterceptDelay=InterceptDelay -local Detection=self.Detection -Detection:SetIntercept(true,InterceptDelay) -return self -end -function AI_A2A_DISPATCHER:GetAIFriendliesNearBy(DetectedItem) -local FriendliesNearBy=self.Detection:GetFriendliesDistance(DetectedItem) -return FriendliesNearBy -end -function AI_A2A_DISPATCHER:GetDefenderTasks() -return self.DefenderTasks or{} -end -function AI_A2A_DISPATCHER:GetDefenderTask(Defender) -return self.DefenderTasks[Defender] -end -function AI_A2A_DISPATCHER:GetDefenderTaskFsm(Defender) -return self:GetDefenderTask(Defender).Fsm -end -function AI_A2A_DISPATCHER:GetDefenderTaskTarget(Defender) -return self:GetDefenderTask(Defender).Target -end -function AI_A2A_DISPATCHER:GetDefenderTaskSquadronName(Defender) -return self:GetDefenderTask(Defender).SquadronName -end -function AI_A2A_DISPATCHER:ClearDefenderTask(Defender) -if Defender and Defender:IsAlive()and self.DefenderTasks[Defender]then -local Target=self.DefenderTasks[Defender].Target -local Message="Clearing ("..self.DefenderTasks[Defender].Type..") " -Message=Message..Defender:GetName() -if Target then -Message=Message..(Target and(" from "..Target.Index.." ["..Target.Set:Count().."]"))or"" -end -self:F({Target=Message}) -end -self.DefenderTasks[Defender]=nil -return self -end -function AI_A2A_DISPATCHER:ClearDefenderTaskTarget(Defender) -local DefenderTask=self:GetDefenderTask(Defender) -if Defender and Defender:IsAlive()and DefenderTask then -local Target=DefenderTask.Target -local Message="Clearing ("..DefenderTask.Type..") " -Message=Message..Defender:GetName() -if Target then -Message=Message..((Target and(" from "..Target.Index.." ["..Target.Set:Count().."]"))or"") -end -self:F({Target=Message}) -end -if Defender and DefenderTask and DefenderTask.Target then -DefenderTask.Target=nil -end -return self -end -function AI_A2A_DISPATCHER:SetDefenderTask(SquadronName,Defender,Type,Fsm,Target) -self:F({SquadronName=SquadronName,Defender=Defender:GetName(),Type=Type,Target=Target}) -self.DefenderTasks[Defender]=self.DefenderTasks[Defender]or{} -self.DefenderTasks[Defender].Type=Type -self.DefenderTasks[Defender].Fsm=Fsm -self.DefenderTasks[Defender].SquadronName=SquadronName -if Target then -self:SetDefenderTaskTarget(Defender,Target) -end -return self -end -function AI_A2A_DISPATCHER:SetDefenderTaskTarget(Defender,AttackerDetection) -local Message="("..self.DefenderTasks[Defender].Type..") " -Message=Message..Defender:GetName() -Message=Message..((AttackerDetection and(" target "..AttackerDetection.Index.." ["..AttackerDetection.Set:Count().."]"))or"") -self:F({AttackerDetection=Message}) -if AttackerDetection then -self.DefenderTasks[Defender].Target=AttackerDetection -end -return self -end -function AI_A2A_DISPATCHER:SetSquadron(SquadronName,AirbaseName,TemplatePrefixes,ResourceCount) -self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} -local DefenderSquadron=self.DefenderSquadrons[SquadronName] -DefenderSquadron.Name=SquadronName -DefenderSquadron.Airbase=AIRBASE:FindByName(AirbaseName) -DefenderSquadron.AirbaseName=DefenderSquadron.Airbase:GetName() -if not DefenderSquadron.Airbase then -error("Cannot find airbase with name:"..AirbaseName) -end -DefenderSquadron.Spawn={} -if type(TemplatePrefixes)=="string"then -local SpawnTemplate=TemplatePrefixes -self.DefenderSpawns[SpawnTemplate]=self.DefenderSpawns[SpawnTemplate]or SPAWN:New(SpawnTemplate) -DefenderSquadron.Spawn[1]=self.DefenderSpawns[SpawnTemplate] -else -for TemplateID,SpawnTemplate in pairs(TemplatePrefixes)do -self.DefenderSpawns[SpawnTemplate]=self.DefenderSpawns[SpawnTemplate]or SPAWN:New(SpawnTemplate) -DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1]=self.DefenderSpawns[SpawnTemplate] -end -end -DefenderSquadron.ResourceCount=ResourceCount -DefenderSquadron.TemplatePrefixes=TemplatePrefixes -DefenderSquadron.Captured=false -self:SetSquadronLanguage(SquadronName,"EN") -self:F({Squadron={SquadronName,AirbaseName,TemplatePrefixes,ResourceCount}}) -return self -end -function AI_A2A_DISPATCHER:GetSquadron(SquadronName) -local DefenderSquadron=self.DefenderSquadrons[SquadronName] -if not DefenderSquadron then -error("Unknown Squadron:"..SquadronName) -end -return DefenderSquadron -end -function AI_A2A_DISPATCHER:QuerySquadron(Squadron) -local Squadron=self:GetSquadron(Squadron) -if Squadron.ResourceCount then -self:T2(string.format("%s = %s",Squadron.Name,Squadron.ResourceCount)) -return Squadron.ResourceCount -end -self:F({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount}) -return nil -end -function AI_A2A_DISPATCHER:SetSquadronVisible(SquadronName) -self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.Uncontrolled=true -DefenderSquadron.Grouping=1 -local nfreeparking=DefenderSquadron.Airbase:GetFreeParkingSpotsNumber(AIRBASE.TerminalType.FighterAircraft,true) -DefenderSquadron.ResourceCount=DefenderSquadron.ResourceCount or nfreeparking -DefenderSquadron.ResourceCount=math.min(DefenderSquadron.ResourceCount,nfreeparking) -for SpawnTemplate,_DefenderSpawn in pairs(self.DefenderSpawns)do -local DefenderSpawn=_DefenderSpawn -DefenderSpawn:InitUnControlled(true) -end -end -function AI_A2A_DISPATCHER:IsSquadronVisible(SquadronName) -self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} -local DefenderSquadron=self:GetSquadron(SquadronName) -if DefenderSquadron then -return DefenderSquadron.Uncontrolled==true -end -return nil -end -function AI_A2A_DISPATCHER:SetSquadronCap2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType) -self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} -self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} -local DefenderSquadron=self:GetSquadron(SquadronName) -local Cap=self.DefenderSquadrons[SquadronName].Cap -Cap.Name=SquadronName -Cap.EngageMinSpeed=EngageMinSpeed -Cap.EngageMaxSpeed=EngageMaxSpeed -Cap.EngageFloorAltitude=EngageFloorAltitude -Cap.EngageCeilingAltitude=EngageCeilingAltitude -Cap.Zone=Zone -Cap.PatrolMinSpeed=PatrolMinSpeed -Cap.PatrolMaxSpeed=PatrolMaxSpeed -Cap.PatrolFloorAltitude=PatrolFloorAltitude -Cap.PatrolCeilingAltitude=PatrolCeilingAltitude -Cap.PatrolAltType=PatrolAltType -Cap.EngageAltType=EngageAltType -self:SetSquadronCapInterval(SquadronName,self.DefenderDefault.CapLimit,self.DefenderDefault.CapMinSeconds,self.DefenderDefault.CapMaxSeconds,1) -self:I({CAP={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageAltType}}) -local RecceSet=self.Detection:GetDetectionSet() -RecceSet:FilterPrefixes(DefenderSquadron.TemplatePrefixes) -RecceSet:FilterStart() -self.Detection:SetFriendlyPrefixes(DefenderSquadron.TemplatePrefixes) -return self -end -function AI_A2A_DISPATCHER:SetSquadronCap(SquadronName,Zone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType) -return self:SetSquadronCap2(SquadronName,EngageMinSpeed,EngageMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,AltType,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,AltType) -end -function AI_A2A_DISPATCHER:SetSquadronCapInterval(SquadronName,CapLimit,LowInterval,HighInterval,Probability) -self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} -self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} -local DefenderSquadron=self:GetSquadron(SquadronName) -local Cap=self.DefenderSquadrons[SquadronName].Cap -if Cap then -Cap.LowInterval=LowInterval or 180 -Cap.HighInterval=HighInterval or 600 -Cap.Probability=Probability or 1 -Cap.CapLimit=CapLimit or 1 -Cap.Scheduler=Cap.Scheduler or SCHEDULER:New(self) -local Scheduler=Cap.Scheduler -local ScheduleID=Cap.ScheduleID -local Variance=(Cap.HighInterval-Cap.LowInterval)/2 -local Repeat=Cap.LowInterval+Variance -local Randomization=Variance/Repeat -local Start=math.random(1,Cap.HighInterval) -if ScheduleID then -Scheduler:Stop(ScheduleID) -end -Cap.ScheduleID=Scheduler:Schedule(self,self.SchedulerCAP,{SquadronName},Start,Repeat,Randomization) -else -error("This squadron does not exist:"..SquadronName) -end -end -function AI_A2A_DISPATCHER:GetCAPDelay(SquadronName) -self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} -self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} -local DefenderSquadron=self:GetSquadron(SquadronName) -local Cap=self.DefenderSquadrons[SquadronName].Cap -if Cap then -return math.random(Cap.LowInterval,Cap.HighInterval) -else -error("This squadron does not exist:"..SquadronName) -end -end -function AI_A2A_DISPATCHER:CanCAP(SquadronName) -self:F({SquadronName=SquadronName}) -self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} -self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} -local DefenderSquadron=self:GetSquadron(SquadronName) -if DefenderSquadron.Captured==false then -if(not DefenderSquadron.ResourceCount)or(DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount>0)then -local Cap=DefenderSquadron.Cap -if Cap then -local CapCount=self:CountCapAirborne(SquadronName) -self:F({CapCount=CapCount}) -if CapCount0)then -local Gci=DefenderSquadron.Gci -if Gci then -return DefenderSquadron -end -end -end -return nil -end -function AI_A2A_DISPATCHER:SetSquadronGci2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} -self.DefenderSquadrons[SquadronName].Gci=self.DefenderSquadrons[SquadronName].Gci or{} -local Intercept=self.DefenderSquadrons[SquadronName].Gci -Intercept.Name=SquadronName -Intercept.EngageMinSpeed=EngageMinSpeed -Intercept.EngageMaxSpeed=EngageMaxSpeed -Intercept.EngageFloorAltitude=EngageFloorAltitude -Intercept.EngageCeilingAltitude=EngageCeilingAltitude -Intercept.EngageAltType=EngageAltType -self:I({GCI={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) -end -function AI_A2A_DISPATCHER:SetSquadronGci(SquadronName,EngageMinSpeed,EngageMaxSpeed) -self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} -self.DefenderSquadrons[SquadronName].Gci=self.DefenderSquadrons[SquadronName].Gci or{} -local Intercept=self.DefenderSquadrons[SquadronName].Gci -Intercept.Name=SquadronName -Intercept.EngageMinSpeed=EngageMinSpeed -Intercept.EngageMaxSpeed=EngageMaxSpeed -self:F({GCI={SquadronName,EngageMinSpeed,EngageMaxSpeed}}) -end -function AI_A2A_DISPATCHER:SetDefaultOverhead(Overhead) -self.DefenderDefault.Overhead=Overhead -return self -end -function AI_A2A_DISPATCHER:SetSquadronOverhead(SquadronName,Overhead) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.Overhead=Overhead -return self -end -function AI_A2A_DISPATCHER:SetDefaultGrouping(Grouping) -self.DefenderDefault.Grouping=Grouping -return self -end -function AI_A2A_DISPATCHER:SetSquadronGrouping(SquadronName,Grouping) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.Grouping=Grouping -return self -end -function AI_A2A_DISPATCHER:SetDefaultTakeoff(Takeoff) -self.DefenderDefault.Takeoff=Takeoff -return self -end -function AI_A2A_DISPATCHER:SetSquadronTakeoff(SquadronName,Takeoff) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.Takeoff=Takeoff -return self -end -function AI_A2A_DISPATCHER:GetDefaultTakeoff() -return self.DefenderDefault.Takeoff -end -function AI_A2A_DISPATCHER:GetSquadronTakeoff(SquadronName) -local DefenderSquadron=self:GetSquadron(SquadronName) -return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff -end -function AI_A2A_DISPATCHER:SetDefaultTakeoffInAir() -self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Air) -return self -end -function AI_A2A_DISPATCHER:SetSendMessages(onoff) -self.SetSendPlayerMessages=onoff -end -function AI_A2A_DISPATCHER:SetSquadronTakeoffInAir(SquadronName,TakeoffAltitude) -self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Air) -if TakeoffAltitude then -self:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude) -end -return self -end -function AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway() -self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Runway) -return self -end -function AI_A2A_DISPATCHER:SetSquadronTakeoffFromRunway(SquadronName) -self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Runway) -return self -end -function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot() -self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Hot) -return self -end -function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingHot(SquadronName) -self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Hot) -return self -end -function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold() -self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Cold) -return self -end -function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingCold(SquadronName) -self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Cold) -return self -end -function AI_A2A_DISPATCHER:SetDefaultTakeoffInAirAltitude(TakeoffAltitude) -self.DefenderDefault.TakeoffAltitude=TakeoffAltitude -return self -end -function AI_A2A_DISPATCHER:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.TakeoffAltitude=TakeoffAltitude -return self -end -function AI_A2A_DISPATCHER:SetDefaultLanding(Landing) -self.DefenderDefault.Landing=Landing -return self -end -function AI_A2A_DISPATCHER:SetSquadronLanding(SquadronName,Landing) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.Landing=Landing -return self -end -function AI_A2A_DISPATCHER:GetDefaultLanding() -return self.DefenderDefault.Landing -end -function AI_A2A_DISPATCHER:GetSquadronLanding(SquadronName) -local DefenderSquadron=self:GetSquadron(SquadronName) -return DefenderSquadron.Landing or self.DefenderDefault.Landing -end -function AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase() -self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.NearAirbase) -return self -end -function AI_A2A_DISPATCHER:SetSquadronLandingNearAirbase(SquadronName) -self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.NearAirbase) -return self -end -function AI_A2A_DISPATCHER:SetDefaultLandingAtRunway() -self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.AtRunway) -return self -end -function AI_A2A_DISPATCHER:SetSquadronLandingAtRunway(SquadronName) -self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.AtRunway) -return self -end -function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() -self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.AtEngineShutdown) -return self -end -function AI_A2A_DISPATCHER:SetSquadronLandingAtEngineShutdown(SquadronName) -self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.AtEngineShutdown) -return self -end -function AI_A2A_DISPATCHER:SetDefaultFuelThreshold(FuelThreshold) -self.DefenderDefault.FuelThreshold=FuelThreshold -return self -end -function AI_A2A_DISPATCHER:SetSquadronFuelThreshold(SquadronName,FuelThreshold) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.FuelThreshold=FuelThreshold -return self -end -function AI_A2A_DISPATCHER:SetDefaultTanker(TankerName) -self.DefenderDefault.TankerName=TankerName -return self -end -function AI_A2A_DISPATCHER:SetSquadronTanker(SquadronName,TankerName) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.TankerName=TankerName -return self -end -function AI_A2A_DISPATCHER:SetSquadronLanguage(SquadronName,Language) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.Language=Language -if DefenderSquadron.RadioQueue then -DefenderSquadron.RadioQueue:SetLanguage(Language) -end -return self -end -function AI_A2A_DISPATCHER:SetSquadronRadioFrequency(SquadronName,RadioFrequency,RadioModulation,RadioPower) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.RadioFrequency=RadioFrequency -DefenderSquadron.RadioModulation=RadioModulation or radio.modulation.AM -DefenderSquadron.RadioPower=RadioPower or 100 -if DefenderSquadron.RadioQueue then -DefenderSquadron.RadioQueue:Stop() -end -DefenderSquadron.RadioQueue=nil -DefenderSquadron.RadioQueue=RADIOSPEECH:New(DefenderSquadron.RadioFrequency,DefenderSquadron.RadioModulation) -DefenderSquadron.RadioQueue.power=DefenderSquadron.RadioPower -DefenderSquadron.RadioQueue:Start(0.5) -DefenderSquadron.RadioQueue:SetLanguage(DefenderSquadron.Language) -end -function AI_A2A_DISPATCHER:AddDefenderToSquadron(Squadron,Defender,Size) -self.Defenders=self.Defenders or{} -local DefenderName=Defender:GetName() -self.Defenders[DefenderName]=Squadron -if Squadron.ResourceCount then -Squadron.ResourceCount=Squadron.ResourceCount-Size -end -self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount}) -end -function AI_A2A_DISPATCHER:RemoveDefenderFromSquadron(Squadron,Defender) -self.Defenders=self.Defenders or{} -local DefenderName=Defender:GetName() -if Squadron.ResourceCount then -Squadron.ResourceCount=Squadron.ResourceCount+Defender:GetSize() -end -self.Defenders[DefenderName]=nil -self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount}) -end -function AI_A2A_DISPATCHER:GetSquadronFromDefender(Defender) -self.Defenders=self.Defenders or{} -if Defender~=nil then -local DefenderName=Defender:GetName() -self:F({DefenderName=DefenderName}) -return self.Defenders[DefenderName] -else -return nil -end -end -function AI_A2A_DISPATCHER:EvaluateSWEEP(DetectedItem) -self:F({DetectedItem.ItemID}) -local DetectedSet=DetectedItem.Set -local DetectedZone=DetectedItem.Zone -if DetectedItem.IsDetected==false then -local TargetSetUnit=SET_UNIT:New() -TargetSetUnit:SetDatabase(DetectedSet) -TargetSetUnit:FilterOnce() -return TargetSetUnit -end -return nil -end -function AI_A2A_DISPATCHER:CountCapAirborne(SquadronName) -local CapCount=0 -local DefenderSquadron=self.DefenderSquadrons[SquadronName] -if DefenderSquadron then -for AIGroup,DefenderTask in pairs(self:GetDefenderTasks())do -if DefenderTask.SquadronName==SquadronName then -if DefenderTask.Type=="CAP"then -if AIGroup and AIGroup:IsAlive()then -if DefenderTask.Fsm:Is("Patrolling")or DefenderTask.Fsm:Is("Engaging")or DefenderTask.Fsm:Is("Refuelling")or DefenderTask.Fsm:Is("Started")then -CapCount=CapCount+1 -end -end -end -end -end -end -return CapCount -end -function AI_A2A_DISPATCHER:CountDefendersEngaged(AttackerDetection) -local DefenderCount=0 -local DetectedSet=AttackerDetection.Set -local DefenderTasks=self:GetDefenderTasks() -for DefenderGroup,DefenderTask in pairs(DefenderTasks)do -local Defender=DefenderGroup -local DefenderTaskTarget=DefenderTask.Target -local DefenderSquadronName=DefenderTask.SquadronName -if DefenderTaskTarget and DefenderTaskTarget.Index==AttackerDetection.Index then -local Squadron=self:GetSquadron(DefenderSquadronName) -local SquadronOverhead=Squadron.Overhead or self.DefenderDefault.Overhead -local DefenderSize=Defender:GetInitialSize() -if DefenderSize then -DefenderCount=DefenderCount+DefenderSize/SquadronOverhead -self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize) -else -DefenderCount=0 -end -end -end -self:F({DefenderCount=DefenderCount}) -return DefenderCount -end -function AI_A2A_DISPATCHER:CountDefendersToBeEngaged(AttackerDetection,DefenderCount) -local Friendlies=nil -local AttackerSet=AttackerDetection.Set -local AttackerCount=AttackerSet:Count() -local DefenderFriendlies=self:GetAIFriendliesNearBy(AttackerDetection) -for FriendlyDistance,AIFriendly in UTILS.spairs(DefenderFriendlies or{})do -if AttackerCount>DefenderCount then -if AIFriendly then -local classname=AIFriendly.ClassName or"No Class Name" -local unitname=AIFriendly.IdentifiableName or"No Unit Name" -end -local Friendly=nil -if AIFriendly and AIFriendly:IsAlive()then -Friendly=AIFriendly:GetGroup() -end -if Friendly and Friendly:IsAlive()then -local DefenderTask=self:GetDefenderTask(Friendly) -if DefenderTask then -if DefenderTask.Type=="CAP"or DefenderTask.Type=="GCI"then -if DefenderTask.Target==nil then -if DefenderTask.Fsm:Is("Returning")or DefenderTask.Fsm:Is("Patrolling")then -Friendlies=Friendlies or{} -Friendlies[Friendly]=Friendly -DefenderCount=DefenderCount+Friendly:GetSize() -self:F({Friendly=Friendly:GetName(),FriendlyDistance=FriendlyDistance}) -end -end -end -end -end -else -break -end -end -return Friendlies -end -function AI_A2A_DISPATCHER:ResourceActivate(DefenderSquadron,DefendersNeeded) -local SquadronName=DefenderSquadron.Name -DefendersNeeded=DefendersNeeded or 4 -local DefenderGrouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping -DefenderGrouping=(DefenderGrouping0 then -local id=math.random(n) -local Defender=self.uncontrolled[SquadronName][id].group -Defender:StartUncontrolled() -DefenderGrouping=self.uncontrolled[SquadronName][id].grouping -self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping) -table.remove(self.uncontrolled[SquadronName],id) -return Defender,DefenderGrouping -else -return nil,0 -end -local TemplateID=math.random(1,#DefenderSquadron.Spawn) -else -local Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)] -if DefenderGrouping then -Spawn:InitGrouping(DefenderGrouping) -else -Spawn:InitGrouping() -end -local TakeoffMethod=self:GetSquadronTakeoff(SquadronName) -local Defender=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,TakeoffMethod,DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude) -self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping) -return Defender,DefenderGrouping -end -return nil,nil -end -function AI_A2A_DISPATCHER:onafterCAP(From,Event,To,SquadronName) -self:F({SquadronName=SquadronName}) -self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} -self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} -local DefenderSquadron=self:CanCAP(SquadronName) -if DefenderSquadron then -local Cap=DefenderSquadron.Cap -if Cap then -local DefenderCAP,DefenderGrouping=self:ResourceActivate(DefenderSquadron) -if DefenderCAP then -local AI_A2A_Fsm=AI_A2A_CAP:New2(DefenderCAP,Cap.EngageMinSpeed,Cap.EngageMaxSpeed,Cap.EngageFloorAltitude,Cap.EngageCeilingAltitude,Cap.EngageAltType,Cap.Zone,Cap.PatrolMinSpeed,Cap.PatrolMaxSpeed,Cap.PatrolFloorAltitude,Cap.PatrolCeilingAltitude,Cap.PatrolAltType) -AI_A2A_Fsm:SetDispatcher(self) -AI_A2A_Fsm:SetHomeAirbase(DefenderSquadron.Airbase) -AI_A2A_Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60) -AI_A2A_Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold) -AI_A2A_Fsm:SetDisengageRadius(self.DisengageRadius) -AI_A2A_Fsm:SetTanker(DefenderSquadron.TankerName or self.DefenderDefault.TankerName) -if DefenderSquadron.Racetrack or self.DefenderDefault.Racetrack then -AI_A2A_Fsm:SetRaceTrackPattern(DefenderSquadron.RacetrackLengthMin or self.DefenderDefault.RacetrackLengthMin, -DefenderSquadron.RacetrackLengthMax or self.DefenderDefault.RacetrackLengthMax, -DefenderSquadron.RacetrackHeadingMin or self.DefenderDefault.RacetrackHeadingMin, -DefenderSquadron.RacetrackHeadingMax or self.DefenderDefault.RacetrackHeadingMax, -DefenderSquadron.RacetrackDurationMin or self.DefenderDefault.RacetrackDurationMin, -DefenderSquadron.RacetrackDurationMax or self.DefenderDefault.RacetrackDurationMax, -DefenderSquadron.RacetrackCoordinates or self.DefenderDefault.RacetrackCoordinates) -end -AI_A2A_Fsm:Start() -self:SetDefenderTask(SquadronName,DefenderCAP,"CAP",AI_A2A_Fsm) -function AI_A2A_Fsm:onafterTakeoff(DefenderGroup,From,Event,To) -if DefenderGroup and DefenderGroup:IsAlive()then -self:F({"CAP Takeoff",DefenderGroup:GetName()}) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=AI_A2A_Fsm:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if Squadron then -if self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName.." Wheels up.",DefenderGroup) -end -AI_A2A_Fsm:__Patrol(2) -end -end -end -function AI_A2A_Fsm:onafterPatrolRoute(DefenderGroup,From,Event,To) -if DefenderGroup and DefenderGroup:IsAlive()then -self:F({"CAP PatrolRoute",DefenderGroup:GetName()}) -self:GetParent(self).onafterPatrolRoute(self,DefenderGroup,From,Event,To) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=self:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if Squadron and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", patrolling.",DefenderGroup) -end -Dispatcher:ClearDefenderTaskTarget(DefenderGroup) -end -end -function AI_A2A_Fsm:onafterRTB(DefenderGroup,From,Event,To) -if DefenderGroup and DefenderGroup:IsAlive()then -self:F({"CAP RTB",DefenderGroup:GetName()}) -self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=self:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if Squadron and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName.." returning to base.",DefenderGroup) -end -Dispatcher:ClearDefenderTaskTarget(DefenderGroup) -end -end -function AI_A2A_Fsm:onafterHome(Defender,From,Event,To,Action) -if Defender and Defender:IsAlive()then -self:F({"CAP Home",Defender:GetName()}) -self:GetParent(self).onafterHome(self,Defender,From,Event,To) -local Dispatcher=self:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(Defender) -if Action and Action=="Destroy"then -Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender) -Defender:Destroy() -end -if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2A_DISPATCHER.Landing.NearAirbase then -Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender) -Defender:Destroy() -Dispatcher:ParkDefender(Squadron) -end -end -end -end -end -end -end -function AI_A2A_DISPATCHER:onafterENGAGE(From,Event,To,AttackerDetection,Defenders) -self:F("ENGAGING Detection ID="..tostring(AttackerDetection.ID)) -if Defenders then -for DefenderID,Defender in pairs(Defenders)do -local Fsm=self:GetDefenderTaskFsm(Defender) -Fsm:EngageRoute(AttackerDetection.Set) -self:SetDefenderTaskTarget(Defender,AttackerDetection) -end -end -end -function AI_A2A_DISPATCHER:onafterGCI(From,Event,To,AttackerDetection,DefendersMissing,DefenderFriendlies) -self:F("GCI Detection ID="..tostring(AttackerDetection.ID)) -self:F({From,Event,To,AttackerDetection.Index,DefendersMissing,DefenderFriendlies}) -local AttackerSet=AttackerDetection.Set -local AttackerUnit=AttackerSet:GetFirst() -if AttackerUnit and AttackerUnit:IsAlive()then -local AttackerCount=AttackerSet:Count() -local DefenderCount=0 -for DefenderID,DefenderGroup in pairs(DefenderFriendlies or{})do -local Fsm=self:GetDefenderTaskFsm(DefenderGroup) -Fsm:__EngageRoute(0.1,AttackerSet) -self:SetDefenderTaskTarget(DefenderGroup,AttackerDetection) -DefenderCount=DefenderCount+DefenderGroup:GetSize() -end -self:F({DefenderCount=DefenderCount,DefendersMissing=DefendersMissing}) -DefenderCount=DefendersMissing -local ClosestDistance=0 -local ClosestDefenderSquadronName=nil -local BreakLoop=false -while(DefenderCount>0 and not BreakLoop)do -self:F({DefenderSquadrons=self.DefenderSquadrons}) -for SquadronName,DefenderSquadron in pairs(self.DefenderSquadrons or{})do -self:F({GCI=DefenderSquadron.Gci}) -for InterceptID,Intercept in pairs(DefenderSquadron.Gci or{})do -self:F({DefenderSquadron}) -local SpawnCoord=DefenderSquadron.Airbase:GetCoordinate() -local AttackerCoord=AttackerUnit:GetCoordinate() -local InterceptCoord=AttackerDetection.InterceptCoord -self:F({InterceptCoord=InterceptCoord}) -if InterceptCoord then -local InterceptDistance=SpawnCoord:Get2DDistance(InterceptCoord) -local AirbaseDistance=SpawnCoord:Get2DDistance(AttackerCoord) -self:F({InterceptDistance=InterceptDistance,AirbaseDistance=AirbaseDistance,InterceptCoord=InterceptCoord}) -if ClosestDistance==0 or InterceptDistanceDefenderSquadron.ResourceCount then -DefendersNeeded=DefenderSquadron.ResourceCount -BreakLoop=true -end -while(DefendersNeeded>0)do -local DefenderGCI,DefenderGrouping=self:ResourceActivate(DefenderSquadron,DefendersNeeded) -DefendersNeeded=DefendersNeeded-DefenderGrouping -if DefenderGCI then -DefenderCount=DefenderCount-DefenderGrouping/DefenderOverhead -local Fsm=AI_A2A_GCI:New2(DefenderGCI,Gci.EngageMinSpeed,Gci.EngageMaxSpeed,Gci.EngageFloorAltitude,Gci.EngageCeilingAltitude,Gci.EngageAltType) -Fsm:SetDispatcher(self) -Fsm:SetHomeAirbase(DefenderSquadron.Airbase) -Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60) -Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold) -Fsm:SetDisengageRadius(self.DisengageRadius) -Fsm:Start() -self:SetDefenderTask(ClosestDefenderSquadronName,DefenderGCI,"GCI",Fsm,AttackerDetection) -function Fsm:onafterTakeoff(DefenderGroup,From,Event,To) -self:F({"GCI Birth",DefenderGroup:GetName()}) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=Fsm:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -local DefenderTarget=Dispatcher:GetDefenderTaskTarget(DefenderGroup) -if DefenderTarget then -if Squadron.Language=="EN"and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName.." wheels up.",DefenderGroup) -elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName.." колёса вверх.",DefenderGroup) -end -Fsm:EngageRoute(DefenderTarget.Set) -end -end -function Fsm:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit) -self:F({"GCI Route",DefenderGroup:GetName()}) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=Fsm:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if Squadron and AttackSetUnit:Count()>0 then -local FirstUnit=AttackSetUnit:GetFirst() -local Coordinate=FirstUnit:GetCoordinate() -if Squadron.Language=="EN"and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", intercepting bogeys at "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) -elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", перехватывая боги в "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) -elseif Squadron.Language=="DE"and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", Eindringlinge abfangen bei"..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) -end -end -self:GetParent(Fsm).onafterEngageRoute(self,DefenderGroup,From,Event,To,AttackSetUnit) -end -function Fsm:onafterEngage(DefenderGroup,From,Event,To,AttackSetUnit) -self:F({"GCI Engage",DefenderGroup:GetName()}) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=Fsm:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if Squadron and AttackSetUnit:Count()>0 then -local FirstUnit=AttackSetUnit:GetFirst() -local Coordinate=FirstUnit:GetCoordinate() -if Squadron.Language=="EN"and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", engaging bogeys at "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) -elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", задействуя боги в "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) -end -end -self:GetParent(Fsm).onafterEngage(self,DefenderGroup,From,Event,To,AttackSetUnit) -end -function Fsm:onafterRTB(DefenderGroup,From,Event,To) -self:F({"GCI RTB",DefenderGroup:GetName()}) -self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=self:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if Squadron then -if Squadron.Language=="EN"and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName.." returning to base.",DefenderGroup) -elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", возвращение на базу.",DefenderGroup) -end -end -Dispatcher:ClearDefenderTaskTarget(DefenderGroup) -end -function Fsm:onafterLostControl(Defender,From,Event,To) -self:F({"GCI LostControl",Defender:GetName()}) -self:GetParent(self).onafterHome(self,Defender,From,Event,To) -local Dispatcher=Fsm:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(Defender) -if Defender:IsAboveRunway()then -Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender) -Defender:Destroy() -end -end -function Fsm:onafterHome(DefenderGroup,From,Event,To,Action) -self:F({"GCI Home",DefenderGroup:GetName()}) -self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=self:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if Squadron.Language=="EN"and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName.." landing at base.",DefenderGroup) -elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", посадка на базу.",DefenderGroup) -end -if Action and Action=="Destroy"then -Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) -DefenderGroup:Destroy() -end -if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2A_DISPATCHER.Landing.NearAirbase then -Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) -DefenderGroup:Destroy() -Dispatcher:ParkDefender(Squadron) -end -end -end -end -end -else -BreakLoop=true -break -end -else -break -end -end -end -end -function AI_A2A_DISPATCHER:EvaluateENGAGE(DetectedItem) -self:F({DetectedItem.ItemID}) -local DefenderCount=self:CountDefendersEngaged(DetectedItem) -local DefenderGroups=self:CountDefendersToBeEngaged(DetectedItem,DefenderCount) -self:F({DefenderCount=DefenderCount}) -if DefenderGroups and DetectedItem.IsDetected==true then -return DefenderGroups -end -return nil -end -function AI_A2A_DISPATCHER:EvaluateGCI(DetectedItem) -self:F({DetectedItem.ItemID}) -local AttackerSet=DetectedItem.Set -local AttackerCount=AttackerSet:Count() -local DefenderCount=self:CountDefendersEngaged(DetectedItem) -local DefendersMissing=AttackerCount-DefenderCount -self:F({AttackerCount=AttackerCount,DefenderCount=DefenderCount,DefendersMissing=DefendersMissing}) -local Friendlies=self:CountDefendersToBeEngaged(DetectedItem,DefenderCount) -if DetectedItem.IsDetected==true then -return DefendersMissing,Friendlies -end -return nil,nil -end -function AI_A2A_DISPATCHER:Order(DetectedItem) -local detection=self.Detection -local ShortestDistance=999999999 -local AttackCoordinate=detection:GetDetectedItemCoordinate(DetectedItem) -if AttackCoordinate then -for DefenderSquadronName,DefenderSquadron in pairs(self.DefenderSquadrons)do -self:T({DefenderSquadron=DefenderSquadron.Name}) -local Airbase=DefenderSquadron.Airbase -local AirbaseCoordinate=Airbase:GetCoordinate() -local EvaluateDistance=AttackCoordinate:Get2DDistance(AirbaseCoordinate) -if EvaluateDistance<=ShortestDistance then -ShortestDistance=EvaluateDistance -end -end -end -return ShortestDistance -end -function AI_A2A_DISPATCHER:ShowTacticalDisplay(Detection) -local AreaMsg={} -local TaskMsg={} -local ChangeMsg={} -local TaskReport=REPORT:New() -local Report=REPORT:New("Tactical Overview:") -local DefenderGroupCount=0 -for DetectedItemID,DetectedItem in UTILS.spairs(Detection:GetDetectedItems(),function(t,a,b) -return self:Order(t[a])0 then -self:F({DefendersMissing=DefendersMissing}) -self:GCI(DetectedItem,DefendersMissing,Friendlies) -end -end -end -if self.TacticalDisplay then -self:ShowTacticalDisplay(Detection) -end -return true -end -end -do -function AI_A2A_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem) -local DetectedSet=DetectedItem.Set -local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem) -local PlayerTypes={} -local PlayersCount=0 -if PlayersNearBy then -local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() -for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do -local PlayerUnit=PlayerUnitData -local PlayerName=PlayerUnit:GetPlayerName() -if PlayerUnit:IsAirPlane()and PlayerName~=nil then -local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel() -PlayersCount=PlayersCount+1 -local PlayerType=PlayerUnit:GetTypeName() -PlayerTypes[PlayerName]=PlayerType -if DetectedTreatLevel0 then -for PlayerName,PlayerType in pairs(PlayerTypes)do -PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType)) -end -else -PlayerTypesReport:Add("-") -end -return PlayersCount,PlayerTypesReport -end -function AI_A2A_DISPATCHER:GetFriendliesNearBy(DetectedItem) -local DetectedSet=DetectedItem.Set -local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem) -local FriendlyTypes={} -local FriendliesCount=0 -if FriendlyUnitsNearBy then -local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() -for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do -local FriendlyUnit=FriendlyUnitData -if FriendlyUnit:IsAirPlane()then -local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel() -FriendliesCount=FriendliesCount+1 -local FriendlyType=FriendlyUnit:GetTypeName() -FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1 -if DetectedTreatLevel0 then -for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do -FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType)) -end -else -FriendlyTypesReport:Add("-") -end -return FriendliesCount,FriendlyTypesReport -end -function AI_A2A_DISPATCHER:SchedulerCAP(SquadronName) -self:CAP(SquadronName) -end -function AI_A2A_DISPATCHER:AddToSquadron(Squadron,Amount) -local Squadron=self:GetSquadron(Squadron) -if Squadron.ResourceCount then -Squadron.ResourceCount=Squadron.ResourceCount+Amount -end -self:T({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount}) -end -function AI_A2A_DISPATCHER:RemoveFromSquadron(Squadron,Amount) -local Squadron=self:GetSquadron(Squadron) -if Squadron.ResourceCount then -Squadron.ResourceCount=Squadron.ResourceCount-Amount -end -self:T({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount}) -end -end -do -AI_A2A_GCICAP={ -ClassName="AI_A2A_GCICAP", -Detection=nil, -} -function AI_A2A_GCICAP:New(EWRPrefixes,TemplatePrefixes,CapPrefixes,CapLimit,GroupingRadius,EngageRadius,GciRadius,ResourceCount) -local EWRSetGroup=SET_GROUP:New() -EWRSetGroup:FilterPrefixes(EWRPrefixes) -EWRSetGroup:FilterStart() -local Detection=DETECTION_AREAS:New(EWRSetGroup,GroupingRadius or 30000) -local self=BASE:Inherit(self,AI_A2A_DISPATCHER:New(Detection)) -self:SetEngageRadius(EngageRadius) -self:SetGciRadius(GciRadius) -local EWRFirst=EWRSetGroup:GetFirst() -local EWRCoalition=EWRFirst:GetCoalition() -local AirbaseNames={} -for AirbaseID,AirbaseData in pairs(_DATABASE.AIRBASES)do -local Airbase=AirbaseData -local AirbaseName=Airbase:GetName() -if Airbase:GetCoalition()==EWRCoalition then -table.insert(AirbaseNames,AirbaseName) -end -end -self.Templates=SET_GROUP:New():FilterPrefixes(TemplatePrefixes):FilterOnce() -self:I({Airbases=AirbaseNames}) -self:I("Defining Templates for Airbases ...") -for AirbaseID,AirbaseName in pairs(AirbaseNames)do -local Airbase=_DATABASE:FindAirbase(AirbaseName) -local AirbaseName=Airbase:GetName() -local AirbaseCoord=Airbase:GetCoordinate() -local AirbaseZone=ZONE_RADIUS:New("Airbase",AirbaseCoord:GetVec2(),3000) -local Templates=nil -self:I({Airbase=AirbaseName}) -for TemplateID,Template in pairs(self.Templates:GetSet())do -local Template=Template -local TemplateCoord=Template:GetCoordinate() -if AirbaseZone:IsVec2InZone(TemplateCoord:GetVec2())then -Templates=Templates or{} -table.insert(Templates,Template:GetName()) -self:I({Template=Template:GetName()}) -end -end -if Templates then -self:SetSquadron(AirbaseName,AirbaseName,Templates,ResourceCount) -end -end -self.CAPTemplates=SET_GROUP:New() -self.CAPTemplates:FilterPrefixes(CapPrefixes) -self.CAPTemplates:FilterOnce() -self:I("Setting up CAP ...") -for CAPID,CAPTemplate in pairs(self.CAPTemplates:GetSet())do -local CAPZone=ZONE_POLYGON:New(CAPTemplate:GetName(),CAPTemplate) -local AirbaseDistance=99999999 -local AirbaseClosest=nil -self:I({CAPZoneGroup=CAPID}) -for AirbaseID,AirbaseName in pairs(AirbaseNames)do -local Airbase=_DATABASE:FindAirbase(AirbaseName) -local AirbaseName=Airbase:GetName() -local AirbaseCoord=Airbase:GetCoordinate() -local Squadron=self.DefenderSquadrons[AirbaseName] -if Squadron then -local Distance=AirbaseCoord:Get2DDistance(CAPZone:GetCoordinate()) -self:I({AirbaseDistance=Distance}) -if Distance0)then -local Patrol=DefenderSquadron[DefenseTaskType] -if Patrol and Patrol.Patrol==true then -local PatrolCount=self:CountPatrolAirborne(SquadronName,DefenseTaskType) -self:F({PatrolCount=PatrolCount,PatrolLimit=Patrol.PatrolLimit,PatrolProbability=Patrol.Probability}) -if PatrolCount0)then -if DefenderSquadron[DefenseTaskType]and(DefenderSquadron[DefenseTaskType].Defend==true)then -return DefenderSquadron,DefenderSquadron[DefenseTaskType] -end -end -end -return nil -end -function AI_A2G_DISPATCHER:SetSquadronEngageLimit(SquadronName,EngageLimit,DefenseTaskType) -local DefenderSquadron=self:GetSquadron(SquadronName) -local Defense=DefenderSquadron[DefenseTaskType] -if Defense then -Defense.EngageLimit=EngageLimit or 1 -else -error("This squadron does not exist:"..SquadronName) -end -end -function AI_A2G_DISPATCHER:SetSquadronSead2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.SEAD=DefenderSquadron.SEAD or{} -local Sead=DefenderSquadron.SEAD -Sead.Name=SquadronName -Sead.EngageMinSpeed=EngageMinSpeed -Sead.EngageMaxSpeed=EngageMaxSpeed -Sead.EngageFloorAltitude=EngageFloorAltitude or 500 -Sead.EngageCeilingAltitude=EngageCeilingAltitude or 1000 -Sead.EngageAltType=EngageAltType -Sead.Defend=true -self:I({SEAD={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) -return self -end -function AI_A2G_DISPATCHER:SetSquadronSead(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude) -return self:SetSquadronSead2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,"RADIO") -end -function AI_A2G_DISPATCHER:SetSquadronSeadEngageLimit(SquadronName,EngageLimit) -self:SetSquadronEngageLimit(SquadronName,EngageLimit,"SEAD") -end -function AI_A2G_DISPATCHER:SetSquadronSeadPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.SEAD=DefenderSquadron.SEAD or{} -local SeadPatrol=DefenderSquadron.SEAD -SeadPatrol.Name=SquadronName -SeadPatrol.Zone=Zone -SeadPatrol.PatrolFloorAltitude=PatrolFloorAltitude -SeadPatrol.PatrolCeilingAltitude=PatrolCeilingAltitude -SeadPatrol.EngageFloorAltitude=EngageFloorAltitude -SeadPatrol.EngageCeilingAltitude=EngageCeilingAltitude -SeadPatrol.PatrolMinSpeed=PatrolMinSpeed -SeadPatrol.PatrolMaxSpeed=PatrolMaxSpeed -SeadPatrol.EngageMinSpeed=EngageMinSpeed -SeadPatrol.EngageMaxSpeed=EngageMaxSpeed -SeadPatrol.PatrolAltType=PatrolAltType -SeadPatrol.EngageAltType=EngageAltType -SeadPatrol.Patrol=true -self:SetSquadronPatrolInterval(SquadronName,self.DefenderDefault.PatrolLimit,self.DefenderDefault.PatrolMinSeconds,self.DefenderDefault.PatrolMaxSeconds,1,"SEAD") -self:I({SEAD={Zone:GetName(),PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) -end -function AI_A2G_DISPATCHER:SetSquadronSeadPatrol(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType) -self:SetSquadronSeadPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,FloorAltitude,CeilingAltitude,AltType,EngageMinSpeed,EngageMaxSpeed,FloorAltitude,CeilingAltitude,AltType) -end -function AI_A2G_DISPATCHER:SetSquadronCas2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.CAS=DefenderSquadron.CAS or{} -local Cas=DefenderSquadron.CAS -Cas.Name=SquadronName -Cas.EngageMinSpeed=EngageMinSpeed -Cas.EngageMaxSpeed=EngageMaxSpeed -Cas.EngageFloorAltitude=EngageFloorAltitude or 500 -Cas.EngageCeilingAltitude=EngageCeilingAltitude or 1000 -Cas.EngageAltType=EngageAltType -Cas.Defend=true -self:I({CAS={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) -return self -end -function AI_A2G_DISPATCHER:SetSquadronCas(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude) -return self:SetSquadronCas2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,"RADIO") -end -function AI_A2G_DISPATCHER:SetSquadronCasEngageLimit(SquadronName,EngageLimit) -self:SetSquadronEngageLimit(SquadronName,EngageLimit,"CAS") -end -function AI_A2G_DISPATCHER:SetSquadronCasPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.CAS=DefenderSquadron.CAS or{} -local CasPatrol=DefenderSquadron.CAS -CasPatrol.Name=SquadronName -CasPatrol.Zone=Zone -CasPatrol.PatrolFloorAltitude=PatrolFloorAltitude -CasPatrol.PatrolCeilingAltitude=PatrolCeilingAltitude -CasPatrol.EngageFloorAltitude=EngageFloorAltitude -CasPatrol.EngageCeilingAltitude=EngageCeilingAltitude -CasPatrol.PatrolMinSpeed=PatrolMinSpeed -CasPatrol.PatrolMaxSpeed=PatrolMaxSpeed -CasPatrol.EngageMinSpeed=EngageMinSpeed -CasPatrol.EngageMaxSpeed=EngageMaxSpeed -CasPatrol.PatrolAltType=PatrolAltType -CasPatrol.EngageAltType=EngageAltType -CasPatrol.Patrol=true -self:SetSquadronPatrolInterval(SquadronName,self.DefenderDefault.PatrolLimit,self.DefenderDefault.PatrolMinSeconds,self.DefenderDefault.PatrolMaxSeconds,1,"CAS") -self:I({CAS={Zone:GetName(),PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) -end -function AI_A2G_DISPATCHER:SetSquadronCasPatrol(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType) -self:SetSquadronCasPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,FloorAltitude,CeilingAltitude,AltType,EngageMinSpeed,EngageMaxSpeed,FloorAltitude,CeilingAltitude,AltType) -end -function AI_A2G_DISPATCHER:SetSquadronBai2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.BAI=DefenderSquadron.BAI or{} -local Bai=DefenderSquadron.BAI -Bai.Name=SquadronName -Bai.EngageMinSpeed=EngageMinSpeed -Bai.EngageMaxSpeed=EngageMaxSpeed -Bai.EngageFloorAltitude=EngageFloorAltitude or 500 -Bai.EngageCeilingAltitude=EngageCeilingAltitude or 1000 -Bai.EngageAltType=EngageAltType -Bai.Defend=true -self:I({BAI={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) -return self -end -function AI_A2G_DISPATCHER:SetSquadronBai(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude) -return self:SetSquadronBai2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,"RADIO") -end -function AI_A2G_DISPATCHER:SetSquadronBaiEngageLimit(SquadronName,EngageLimit) -self:SetSquadronEngageLimit(SquadronName,EngageLimit,"BAI") -end -function AI_A2G_DISPATCHER:SetSquadronBaiPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.BAI=DefenderSquadron.BAI or{} -local BaiPatrol=DefenderSquadron.BAI -BaiPatrol.Name=SquadronName -BaiPatrol.Zone=Zone -BaiPatrol.PatrolFloorAltitude=PatrolFloorAltitude -BaiPatrol.PatrolCeilingAltitude=PatrolCeilingAltitude -BaiPatrol.EngageFloorAltitude=EngageFloorAltitude -BaiPatrol.EngageCeilingAltitude=EngageCeilingAltitude -BaiPatrol.PatrolMinSpeed=PatrolMinSpeed -BaiPatrol.PatrolMaxSpeed=PatrolMaxSpeed -BaiPatrol.EngageMinSpeed=EngageMinSpeed -BaiPatrol.EngageMaxSpeed=EngageMaxSpeed -BaiPatrol.PatrolAltType=PatrolAltType -BaiPatrol.EngageAltType=EngageAltType -BaiPatrol.Patrol=true -self:SetSquadronPatrolInterval(SquadronName,self.DefenderDefault.PatrolLimit,self.DefenderDefault.PatrolMinSeconds,self.DefenderDefault.PatrolMaxSeconds,1,"BAI") -self:I({BAI={Zone:GetName(),PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) -end -function AI_A2G_DISPATCHER:SetSquadronBaiPatrol(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType) -self:SetSquadronBaiPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,FloorAltitude,CeilingAltitude,AltType,EngageMinSpeed,EngageMaxSpeed,FloorAltitude,CeilingAltitude,AltType) -end -function AI_A2G_DISPATCHER:SetDefaultOverhead(Overhead) -self.DefenderDefault.Overhead=Overhead -return self -end -function AI_A2G_DISPATCHER:SetSquadronOverhead(SquadronName,Overhead) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.Overhead=Overhead -return self -end -function AI_A2G_DISPATCHER:GetSquadronOverhead(SquadronName) -local DefenderSquadron=self:GetSquadron(SquadronName) -return DefenderSquadron.Overhead or self.DefenderDefault.Overhead -end -function AI_A2G_DISPATCHER:SetDefaultGrouping(Grouping) -self.DefenderDefault.Grouping=Grouping -return self -end -function AI_A2G_DISPATCHER:SetSquadronGrouping(SquadronName,Grouping) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.Grouping=Grouping -return self -end -function AI_A2G_DISPATCHER:SetSquadronEngageProbability(SquadronName,EngageProbability) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.EngageProbability=EngageProbability -return self -end -function AI_A2G_DISPATCHER:SetDefaultTakeoff(Takeoff) -self.DefenderDefault.Takeoff=Takeoff -return self -end -function AI_A2G_DISPATCHER:SetSquadronTakeoff(SquadronName,Takeoff) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.Takeoff=Takeoff -return self -end -function AI_A2G_DISPATCHER:GetDefaultTakeoff() -return self.DefenderDefault.Takeoff -end -function AI_A2G_DISPATCHER:GetSquadronTakeoff(SquadronName) -local DefenderSquadron=self:GetSquadron(SquadronName) -return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff -end -function AI_A2G_DISPATCHER:SetDefaultTakeoffInAir() -self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Air) -return self -end -function AI_A2G_DISPATCHER:SetSquadronTakeoffInAir(SquadronName,TakeoffAltitude) -self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Air) -if TakeoffAltitude then -self:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude) -end -return self -end -function AI_A2G_DISPATCHER:SetDefaultTakeoffFromRunway() -self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Runway) -return self -end -function AI_A2G_DISPATCHER:SetSquadronTakeoffFromRunway(SquadronName) -self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Runway) -return self -end -function AI_A2G_DISPATCHER:SetDefaultTakeoffFromParkingHot() -self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Hot) -return self -end -function AI_A2G_DISPATCHER:SetSquadronTakeoffFromParkingHot(SquadronName) -self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Hot) -return self -end -function AI_A2G_DISPATCHER:SetDefaultTakeoffFromParkingCold() -self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Cold) -return self -end -function AI_A2G_DISPATCHER:SetSquadronTakeoffFromParkingCold(SquadronName) -self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Cold) -return self -end -function AI_A2G_DISPATCHER:SetDefaultTakeoffInAirAltitude(TakeoffAltitude) -self.DefenderDefault.TakeoffAltitude=TakeoffAltitude -return self -end -function AI_A2G_DISPATCHER:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.TakeoffAltitude=TakeoffAltitude -return self -end -function AI_A2G_DISPATCHER:SetDefaultLanding(Landing) -self.DefenderDefault.Landing=Landing -return self -end -function AI_A2G_DISPATCHER:SetSquadronLanding(SquadronName,Landing) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.Landing=Landing -return self -end -function AI_A2G_DISPATCHER:GetDefaultLanding() -return self.DefenderDefault.Landing -end -function AI_A2G_DISPATCHER:GetSquadronLanding(SquadronName) -local DefenderSquadron=self:GetSquadron(SquadronName) -return DefenderSquadron.Landing or self.DefenderDefault.Landing -end -function AI_A2G_DISPATCHER:SetDefaultLandingNearAirbase() -self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.NearAirbase) -return self -end -function AI_A2G_DISPATCHER:SetSquadronLandingNearAirbase(SquadronName) -self:SetSquadronLanding(SquadronName,AI_A2G_DISPATCHER.Landing.NearAirbase) -return self -end -function AI_A2G_DISPATCHER:SetDefaultLandingAtRunway() -self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.AtRunway) -return self -end -function AI_A2G_DISPATCHER:SetSquadronLandingAtRunway(SquadronName) -self:SetSquadronLanding(SquadronName,AI_A2G_DISPATCHER.Landing.AtRunway) -return self -end -function AI_A2G_DISPATCHER:SetDefaultLandingAtEngineShutdown() -self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.AtEngineShutdown) -return self -end -function AI_A2G_DISPATCHER:SetSquadronLandingAtEngineShutdown(SquadronName) -self:SetSquadronLanding(SquadronName,AI_A2G_DISPATCHER.Landing.AtEngineShutdown) -return self -end -function AI_A2G_DISPATCHER:SetDefaultFuelThreshold(FuelThreshold) -self.DefenderDefault.FuelThreshold=FuelThreshold -return self -end -function AI_A2G_DISPATCHER:SetSquadronFuelThreshold(SquadronName,FuelThreshold) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.FuelThreshold=FuelThreshold -return self -end -function AI_A2G_DISPATCHER:SetDefaultTanker(TankerName) -self.DefenderDefault.TankerName=TankerName -return self -end -function AI_A2G_DISPATCHER:SetSquadronTanker(SquadronName,TankerName) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.TankerName=TankerName -return self -end -function AI_A2G_DISPATCHER:SetSquadronRadioFrequency(SquadronName,RadioFrequency,RadioModulation,RadioPower) -local DefenderSquadron=self:GetSquadron(SquadronName) -DefenderSquadron.RadioFrequency=RadioFrequency -DefenderSquadron.RadioModulation=RadioModulation or radio.modulation.AM -DefenderSquadron.RadioPower=RadioPower or 100 -if DefenderSquadron.RadioQueue then -DefenderSquadron.RadioQueue:Stop() -end -DefenderSquadron.RadioQueue=nil -DefenderSquadron.RadioQueue=RADIOSPEECH:New(DefenderSquadron.RadioFrequency,DefenderSquadron.RadioModulation) -DefenderSquadron.RadioQueue.power=DefenderSquadron.RadioPower -DefenderSquadron.RadioQueue:Start(0.5) -DefenderSquadron.RadioQueue:SetLanguage(DefenderSquadron.Language) -end -function AI_A2G_DISPATCHER:AddDefenderToSquadron(Squadron,Defender,Size) -self.Defenders=self.Defenders or{} -local DefenderName=Defender:GetName() -self.Defenders[DefenderName]=Squadron -if Squadron.ResourceCount then -Squadron.ResourceCount=Squadron.ResourceCount-Size -end -self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount}) -end -function AI_A2G_DISPATCHER:RemoveDefenderFromSquadron(Squadron,Defender) -self.Defenders=self.Defenders or{} -local DefenderName=Defender:GetName() -if Squadron.ResourceCount then -Squadron.ResourceCount=Squadron.ResourceCount+Defender:GetSize() -end -self.Defenders[DefenderName]=nil -self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount}) -end -function AI_A2G_DISPATCHER:GetSquadronFromDefender(Defender) -self.Defenders=self.Defenders or{} -local DefenderName=Defender:GetName() -self:F({DefenderName=DefenderName}) -return self.Defenders[DefenderName] -end -function AI_A2G_DISPATCHER:CountPatrolAirborne(SquadronName,DefenseTaskType) -local PatrolCount=0 -local DefenderSquadron=self.DefenderSquadrons[SquadronName] -if DefenderSquadron then -for AIGroup,DefenderTask in pairs(self:GetDefenderTasks())do -if DefenderTask.SquadronName==SquadronName then -if DefenderTask.Type==DefenseTaskType then -if AIGroup:IsAlive()then -if DefenderTask.Fsm:Is("Patrolling")or DefenderTask.Fsm:Is("Engaging")or DefenderTask.Fsm:Is("Refuelling") -or DefenderTask.Fsm:Is("Started")then -PatrolCount=PatrolCount+1 -end -end -end -end -end -end -return PatrolCount -end -function AI_A2G_DISPATCHER:CountDefendersEngaged(AttackerDetection,AttackerCount) -local DefendersEngaged=0 -local DefendersTotal=0 -local AttackerSet=AttackerDetection.Set -local DefendersMissing=AttackerCount -local DefenderTasks=self:GetDefenderTasks() -for DefenderGroup,DefenderTask in pairs(DefenderTasks)do -local Defender=DefenderGroup -local DefenderTaskTarget=DefenderTask.Target -local DefenderSquadronName=DefenderTask.SquadronName -local DefenderSize=DefenderTask.Size -if DefenderTask.Target then -self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize) -DefendersTotal=DefendersTotal+DefenderSize -if DefenderTaskTarget and DefenderTaskTarget.Index==AttackerDetection.Index then -local SquadronOverhead=self:GetSquadronOverhead(DefenderSquadronName) -self:F({SquadronOverhead=SquadronOverhead}) -if DefenderSize then -DefendersEngaged=DefendersEngaged+DefenderSize -DefendersMissing=DefendersMissing-DefenderSize/SquadronOverhead -self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize) -else -DefendersEngaged=0 -end -end -end -end -for QueueID,QueueItem in pairs(self.DefenseQueue)do -local QueueItem=QueueItem -if QueueItem.AttackerDetection and QueueItem.AttackerDetection.ItemID==AttackerDetection.ItemID then -DefendersMissing=DefendersMissing-QueueItem.DefendersNeeded/QueueItem.DefenderSquadron.Overhead -self:F({QueueItemName=QueueItem.Defense,QueueItem_ItemID=QueueItem.AttackerDetection.ItemID,DetectedItem=AttackerDetection.ItemID,DefendersMissing=DefendersMissing}) -end -end -self:F({DefenderCount=DefendersEngaged}) -return DefendersTotal,DefendersEngaged,DefendersMissing -end -function AI_A2G_DISPATCHER:CountDefenders(AttackerDetection,DefenderCount,DefenderTaskType) -local Friendlies=nil -local AttackerSet=AttackerDetection.Set -local AttackerCount=AttackerSet:Count() -local DefenderFriendlies=self:GetDefenderFriendliesNearBy(AttackerDetection) -for FriendlyDistance,DefenderFriendlyUnit in UTILS.spairs(DefenderFriendlies or{})do -if AttackerCount>DefenderCount then -local FriendlyGroup=DefenderFriendlyUnit:GetGroup() -if FriendlyGroup and FriendlyGroup:IsAlive()then -local DefenderTask=self:GetDefenderTask(FriendlyGroup) -if DefenderTask then -if DefenderTaskType==DefenderTask.Type then -if DefenderTask.Target==nil then -if DefenderTask.Fsm:Is("Returning") -or DefenderTask.Fsm:Is("Patrolling")then -Friendlies=Friendlies or{} -Friendlies[FriendlyGroup]=FriendlyGroup -DefenderCount=DefenderCount+FriendlyGroup:GetSize() -self:F({Friendly=FriendlyGroup:GetName(),FriendlyDistance=FriendlyDistance}) -end -end -end -end -end -else -break -end -end -return Friendlies -end -function AI_A2G_DISPATCHER:ResourceActivate(DefenderSquadron,DefendersNeeded) -local SquadronName=DefenderSquadron.Name -DefendersNeeded=DefendersNeeded or 4 -local DefenderGrouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping -DefenderGrouping=(DefenderGroupingDefenderGrouping then -break -end -end -if DefenderPatrolTemplate then -local TakeoffMethod=self:GetSquadronTakeoff(SquadronName) -local SpawnGroup=GROUP:Register(DefenderName) -DefenderPatrolTemplate.lateActivation=nil -DefenderPatrolTemplate.uncontrolled=nil -local Takeoff=self:GetSquadronTakeoff(SquadronName) -DefenderPatrolTemplate.route.points[1].type=GROUPTEMPLATE.Takeoff[Takeoff][1] -DefenderPatrolTemplate.route.points[1].action=GROUPTEMPLATE.Takeoff[Takeoff][2] -local Defender=_DATABASE:Spawn(DefenderPatrolTemplate) -self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping) -Defender:Activate() -return Defender,DefenderGrouping -end -else -local Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)] -if DefenderGrouping then -Spawn:InitGrouping(DefenderGrouping) -else -Spawn:InitGrouping() -end -local TakeoffMethod=self:GetSquadronTakeoff(SquadronName) -local Defender=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,TakeoffMethod,DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude) -self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping) -return Defender,DefenderGrouping -end -return nil,nil -end -function AI_A2G_DISPATCHER:onafterPatrol(From,Event,To,SquadronName,DefenseTaskType) -local DefenderSquadron,Patrol=self:CanPatrol(SquadronName,DefenseTaskType) -if DefenderSquadron then -local DefendersNeeded -local DefendersGrouping=(DefenderSquadron.Grouping or self.DefenderDefault.Grouping) -if DefenderSquadron.ResourceCount==nil then -DefendersNeeded=DefendersGrouping -else -if DefenderSquadron.ResourceCount>=DefendersGrouping then -DefendersNeeded=DefendersGrouping -else -DefendersNeeded=DefenderSquadron.ResourceCount -end -end -if Patrol then -self:ResourceQueue(true,DefenderSquadron,DefendersNeeded,Patrol,DefenseTaskType,nil,SquadronName) -end -end -end -function AI_A2G_DISPATCHER:ResourceQueue(Patrol,DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,AttackerDetection,SquadronName) -self:F({DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,AttackerDetection,SquadronName}) -local DefenseQueueItem={} -DefenseQueueItem.Patrol=Patrol -DefenseQueueItem.DefenderSquadron=DefenderSquadron -DefenseQueueItem.DefendersNeeded=DefendersNeeded -DefenseQueueItem.Defense=Defense -DefenseQueueItem.DefenseTaskType=DefenseTaskType -DefenseQueueItem.AttackerDetection=AttackerDetection -DefenseQueueItem.SquadronName=SquadronName -table.insert(self.DefenseQueue,DefenseQueueItem) -self:F({QueueItems=#self.DefenseQueue}) -end -function AI_A2G_DISPATCHER:ResourceTakeoff() -for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do -self:F({DefenseQueueID}) -end -for SquadronName,Squadron in pairs(self.DefenderSquadrons)do -if#self.DefenseQueue>0 then -self:F({SquadronName,Squadron.Name,Squadron.TakeoffTime,Squadron.TakeoffInterval,timer.getTime()}) -local DefenseQueueItem=self.DefenseQueue[1] -self:F({DefenderSquadron=DefenseQueueItem.DefenderSquadron}) -if DefenseQueueItem.SquadronName==SquadronName then -if Squadron.TakeoffTime+Squadron.TakeoffInterval0 then -local FirstUnit=AttackSetUnit:GetFirst() -local Coordinate=FirstUnit:GetCoordinate() -if self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", moving on to ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) -end -end -end -function AI_A2G_Fsm:OnAfterEngage(DefenderGroup,From,Event,To,AttackSetUnit) -self:F({"Engage Route",DefenderGroup:GetName()}) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=AI_A2G_Fsm:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -local FirstUnit=AttackSetUnit:GetFirst() -if FirstUnit then -local Coordinate=FirstUnit:GetCoordinate() -if self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", engaging ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) -end -end -end -function AI_A2G_Fsm:onafterRTB(DefenderGroup,From,Event,To) -self:F({"RTB",DefenderGroup:GetName()}) -self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=self:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", returning to base.",DefenderGroup) -end -Dispatcher:ClearDefenderTaskTarget(DefenderGroup) -end -function AI_A2G_Fsm:onafterLostControl(DefenderGroup,From,Event,To) -self:F({"LostControl",DefenderGroup:GetName()}) -self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=AI_A2G_Fsm:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", lost control.") -end -if DefenderGroup:IsAboveRunway()then -Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) -DefenderGroup:Destroy() -end -end -function AI_A2G_Fsm:onafterHome(DefenderGroup,From,Event,To,Action) -self:F({"Home",DefenderGroup:GetName()}) -self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=self:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", landing at base.",DefenderGroup) -end -if Action and Action=="Destroy"then -Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) -DefenderGroup:Destroy() -end -if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2G_DISPATCHER.Landing.NearAirbase then -Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) -DefenderGroup:Destroy() -Dispatcher:ResourcePark(Squadron,DefenderGroup) -end -end -end -end -function AI_A2G_DISPATCHER:ResourceEngage(DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,AttackerDetection,SquadronName) -self:F({DefenderSquadron=DefenderSquadron}) -self:F({DefendersNeeded=DefendersNeeded}) -self:F({Defense=Defense}) -self:F({DefenseTaskType=DefenseTaskType}) -self:F({AttackerDetection=AttackerDetection}) -self:F({SquadronName=SquadronName}) -local DefenderGroup,DefenderGrouping=self:ResourceActivate(DefenderSquadron,DefendersNeeded) -if DefenderGroup then -local AI_A2G_ENGAGE={SEAD=AI_A2G_SEAD,BAI=AI_A2G_BAI,CAS=AI_A2G_CAS} -local AI_A2G_Fsm=AI_A2G_ENGAGE[DefenseTaskType]:New(DefenderGroup,Defense.EngageMinSpeed,Defense.EngageMaxSpeed,Defense.EngageFloorAltitude,Defense.EngageCeilingAltitude,Defense.EngageAltType) -AI_A2G_Fsm:SetDispatcher(self) -AI_A2G_Fsm:SetHomeAirbase(DefenderSquadron.Airbase) -AI_A2G_Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60) -AI_A2G_Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold) -AI_A2G_Fsm:SetDisengageRadius(self.DisengageRadius) -AI_A2G_Fsm:Start() -self:SetDefenderTask(SquadronName,DefenderGroup,DefenseTaskType,AI_A2G_Fsm,AttackerDetection,DefenderGrouping) -function AI_A2G_Fsm:onafterTakeoff(DefenderGroup,From,Event,To) -self:F({"Defender Birth",DefenderGroup:GetName()}) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=AI_A2G_Fsm:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -local DefenderTarget=Dispatcher:GetDefenderTaskTarget(DefenderGroup) -self:F({DefenderTarget=DefenderTarget}) -if DefenderTarget then -if self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", wheels up.",DefenderGroup) -end -AI_A2G_Fsm:EngageRoute(DefenderTarget.Set) -end -end -function AI_A2G_Fsm:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit) -self:F({"Engage Route",DefenderGroup:GetName()}) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=AI_A2G_Fsm:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if Squadron then -local FirstUnit=AttackSetUnit:GetFirst() -local Coordinate=FirstUnit:GetCoordinate() -if self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", on route to ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) -end -end -self:GetParent(self).onafterEngageRoute(self,DefenderGroup,From,Event,To,AttackSetUnit) -end -function AI_A2G_Fsm:OnAfterEngage(DefenderGroup,From,Event,To,AttackSetUnit) -self:F({"Engage Route",DefenderGroup:GetName()}) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=AI_A2G_Fsm:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -local FirstUnit=AttackSetUnit:GetFirst() -if FirstUnit then -local Coordinate=FirstUnit:GetCoordinate() -if self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", engaging ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) -end -end -end -function AI_A2G_Fsm:onafterRTB(DefenderGroup,From,Event,To) -self:F({"Defender RTB",DefenderGroup:GetName()}) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=self:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", returning to base.",DefenderGroup) -end -self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To) -Dispatcher:ClearDefenderTaskTarget(DefenderGroup) -end -function AI_A2G_Fsm:onafterLostControl(DefenderGroup,From,Event,To) -self:F({"Defender LostControl",DefenderGroup:GetName()}) -self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=AI_A2G_Fsm:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,"Squadron "..Squadron.Name..", "..DefenderName.." lost control.") -end -if DefenderGroup:IsAboveRunway()then -Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) -DefenderGroup:Destroy() -end -end -function AI_A2G_Fsm:onafterHome(DefenderGroup,From,Event,To,Action) -self:F({"Defender Home",DefenderGroup:GetName()}) -self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) -local DefenderName=DefenderGroup:GetCallsign() -local Dispatcher=self:GetDispatcher() -local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) -if self.SetSendPlayerMessages then -Dispatcher:MessageToPlayers(Squadron,DefenderName..", landing at base.",DefenderGroup) -end -if Action and Action=="Destroy"then -Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) -DefenderGroup:Destroy() -end -if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2G_DISPATCHER.Landing.NearAirbase then -Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) -DefenderGroup:Destroy() -Dispatcher:ResourcePark(Squadron,DefenderGroup) -end -end -end -end -function AI_A2G_DISPATCHER:onafterEngage(From,Event,To,AttackerDetection,Defenders) -if Defenders then -for DefenderID,Defender in pairs(Defenders or{})do -local Fsm=self:GetDefenderTaskFsm(Defender) -Fsm:Engage(AttackerDetection.Set) -self:SetDefenderTaskTarget(Defender,AttackerDetection) -end -end -end -function AI_A2G_DISPATCHER:HasDefenseLine(DefenseCoordinate,DetectedItem) -local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem) -local EvaluateDistance=AttackCoordinate:Get2DDistance(DefenseCoordinate) -local c1=DefenseCoordinate -local c2=AttackCoordinate -local a=c1.z-c2.z -local b=c2.x-c1.x -local c=c1.x*c2.z-c2.x*c1.z -local ok=true -for AttackItemID,CheckAttackItem in pairs(self.Detection:GetDetectedItems())do -if AttackItemID~=DetectedItem.ID then -local CheckAttackCoordinate=self.Detection:GetDetectedItemCoordinate(CheckAttackItem) -local x=CheckAttackCoordinate.x -local y=CheckAttackCoordinate.z -local r=5000 -local IntersectDistance=(math.abs(a*x+b*y+c))/math.sqrt(a*a+b*b) -self:F({IntersectDistance=IntersectDistance,x=x,y=y}) -local IntersectAttackDistance=CheckAttackCoordinate:Get2DDistance(DefenseCoordinate) -self:F({IntersectAttackDistance=IntersectAttackDistance,EvaluateDistance=EvaluateDistance}) -if IntersectDistance0 and not BreakLoop)do -self:F({DefenderSquadrons=self.DefenderSquadrons}) -for SquadronName,DefenderSquadron in UTILS.rpairs(self.DefenderSquadrons or{})do -if DefenderSquadron[DefenseTaskType]then -local AirbaseCoordinate=DefenderSquadron.Airbase:GetCoordinate() -local AttackerCoord=AttackerUnit:GetCoordinate() -local InterceptCoord=DetectedItem.InterceptCoord -self:F({InterceptCoord=InterceptCoord}) -if InterceptCoord then -local InterceptDistance=AirbaseCoordinate:Get2DDistance(InterceptCoord) -local AirbaseDistance=AirbaseCoordinate:Get2DDistance(AttackerCoord) -self:F({InterceptDistance=InterceptDistance,AirbaseDistance=AirbaseDistance,InterceptCoord=InterceptCoord}) -if AirbaseDistance<=self.DefenseRadius then -local HasDefenseLine=self:HasDefenseLine(AirbaseCoordinate,DetectedItem) -if HasDefenseLine==true then -local EngageProbability=(DefenderSquadron.EngageProbability or 1) -local Probability=math.random() -if Probability=DefendersLimit then -DefendersNeeded=0 -BreakLoop=true -else -if DefendersTotal+DefendersNeeded>DefendersLimit then -DefendersNeeded=DefendersLimit-DefendersTotal -end -end -end -if DefenderSquadron.ResourceCount and DefendersNeeded>DefenderSquadron.ResourceCount then -DefendersNeeded=DefenderSquadron.ResourceCount -BreakLoop=true -end -while(DefendersNeeded>0)do -self:ResourceQueue(false,DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,DetectedItem,EngageSquadronName) -DefendersNeeded=DefendersNeeded-DefenderGrouping -DefenderCount=DefenderCount-DefenderGrouping/DefenderOverhead -end -else -BreakLoop=true -break -end -else -break -end -end -end -end -function AI_A2G_DISPATCHER:Evaluate_SEAD(DetectedItem) -self:F({DetectedItem.ItemID}) -local AttackerSet=DetectedItem.Set -local AttackerCount=AttackerSet:HasSEAD() -if(AttackerCount>0)then -local DefendersTotal,DefendersEngaged,DefendersMissing=self:CountDefendersEngaged(DetectedItem,AttackerCount) -self:F({AttackerCount=AttackerCount,DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) -local DefenderGroups=self:CountDefenders(DetectedItem,DefendersEngaged,"SEAD") -if DetectedItem.IsDetected==true then -return DefendersTotal,DefendersEngaged,DefendersMissing,DefenderGroups -end -end -return 0,0,0 -end -function AI_A2G_DISPATCHER:Evaluate_CAS(DetectedItem) -self:F({DetectedItem.ItemID}) -local AttackerSet=DetectedItem.Set -local AttackerCount=AttackerSet:Count() -local AttackerRadarCount=AttackerSet:HasSEAD() -local IsFriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) -local IsCas=(AttackerRadarCount==0)and(IsFriendliesNearBy==true) -if IsCas==true then -local DefendersTotal,DefendersEngaged,DefendersMissing=self:CountDefendersEngaged(DetectedItem,AttackerCount) -self:F({AttackerCount=AttackerCount,DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) -local DefenderGroups=self:CountDefenders(DetectedItem,DefendersEngaged,"CAS") -if DetectedItem.IsDetected==true then -return DefendersTotal,DefendersEngaged,DefendersMissing,DefenderGroups -end -end -return 0,0,0 -end -function AI_A2G_DISPATCHER:Evaluate_BAI(DetectedItem) -self:F({DetectedItem.ItemID}) -local AttackerSet=DetectedItem.Set -local AttackerCount=AttackerSet:Count() -local AttackerRadarCount=AttackerSet:HasSEAD() -local IsFriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) -local IsBai=(AttackerRadarCount==0)and(IsFriendliesNearBy==false) -if IsBai==true then -local DefendersTotal,DefendersEngaged,DefendersMissing=self:CountDefendersEngaged(DetectedItem,AttackerCount) -self:F({AttackerCount=AttackerCount,DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) -local DefenderGroups=self:CountDefenders(DetectedItem,DefendersEngaged,"BAI") -if DetectedItem.IsDetected==true then -return DefendersTotal,DefendersEngaged,DefendersMissing,DefenderGroups -end -end -return 0,0,0 -end -function AI_A2G_DISPATCHER:Keys(DetectedItem) -self:F({DetectedItem=DetectedItem}) -local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem) -local ShortestDistance=999999999 -for DefenseCoordinateName,DefenseCoordinate in pairs(self.DefenseCoordinates)do -local DefenseCoordinate=DefenseCoordinate -local EvaluateDistance=AttackCoordinate:Get2DDistance(DefenseCoordinate) -if EvaluateDistance<=ShortestDistance then -ShortestDistance=EvaluateDistance -end -end -return ShortestDistance -end -function AI_A2G_DISPATCHER:Order(DetectedItem) -local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem) -local ShortestDistance=999999999 -for DefenseCoordinateName,DefenseCoordinate in pairs(self.DefenseCoordinates)do -local DefenseCoordinate=DefenseCoordinate -local EvaluateDistance=AttackCoordinate:Get2DDistance(DefenseCoordinate) -if EvaluateDistance<=ShortestDistance then -ShortestDistance=EvaluateDistance -end -end -return ShortestDistance -end -function AI_A2G_DISPATCHER:ShowTacticalDisplay(Detection) -local AreaMsg={} -local TaskMsg={} -local ChangeMsg={} -local TaskReport=REPORT:New() -local DefenseTotal=0 -local Report=REPORT:New("\nTactical Overview") -local DefenderGroupCount=0 -local DefendersTotal=0 -for DetectedItemID,DetectedItem in UTILS.spairs(Detection:GetDetectedItems(),function(t,a,b)return self:Order(t[a])0 then -self:F({DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) -self:Defend(DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies,"SEAD") -end -end -do -local DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies=self:Evaluate_CAS(DetectedItem) -if DefendersMissing>0 then -self:F({DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) -self:Defend(DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies,"CAS") -end -end -do -local DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies=self:Evaluate_BAI(DetectedItem) -if DefendersMissing>0 then -self:F({DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) -self:Defend(DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies,"BAI") -end -end -end -for Defender,DefenderTask in pairs(self:GetDefenderTasks())do -local Defender=Defender -if DefenderTask.Target and DefenderTask.Target.Index==DetectedItem.Index then -DefenseTotal=DefenseTotal+1 -end -end -for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do -local DefenseQueueItem=DefenseQueueItem -if DefenseQueueItem.AttackerDetection and DefenseQueueItem.AttackerDetection.Index and DefenseQueueItem.AttackerDetection.Index==DetectedItem.Index then -DefenseTotal=DefenseTotal+1 -end -end -if self.TacticalDisplay then -local ThreatLevel=DetectedItem.Set:CalculateThreatLevelA2G() -Report:Add(string.format(" - %1s%s ( %4s ): ( #%d - %4s ) %s",(DetectedItem.IsDetected==true)and"!"or" ",DetectedItem.ItemID,DetectedItem.Index,DetectedItem.Set:Count(),DetectedItem.Type or" --- ",string.rep("■",ThreatLevel))) -for Defender,DefenderTask in pairs(self:GetDefenderTasks())do -local Defender=Defender -if DefenderTask.Target and DefenderTask.Target.Index==DetectedItem.Index then -if Defender:IsAlive()then -DefenderGroupCount=DefenderGroupCount+1 -local Fuel=Defender:GetFuelMin()*100 -local Damage=Defender:GetLife()/Defender:GetLife0()*100 -Report:Add(string.format(" - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", -Defender:GetName(), -DefenderTask.Type, -DefenderTask.Fsm:GetState(), -Defender:GetSize(), -Fuel, -Damage, -Defender:HasTask()==true and"Executing"or"Idle")) -end -end -end -end -end -end -if self.TacticalDisplay then -Report:Add("\n - No Targets:") -local TaskCount=0 -for Defender,DefenderTask in pairs(self:GetDefenderTasks())do -TaskCount=TaskCount+1 -local Defender=Defender -if not DefenderTask.Target then -if Defender:IsAlive()then -local DefenderHasTask=Defender:HasTask() -local Fuel=Defender:GetFuelMin()*100 -local Damage=Defender:GetLife()/Defender:GetLife0()*100 -DefenderGroupCount=DefenderGroupCount+1 -Report:Add(string.format(" - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", -Defender:GetName(), -DefenderTask.Type, -DefenderTask.Fsm:GetState(), -Defender:GetSize(), -Fuel, -Damage, -Defender:HasTask()==true and"Executing"or"Idle")) -end -end -end -Report:Add(string.format("\n - %d Tasks - %d Defender Groups",TaskCount,DefenderGroupCount)) -Report:Add(string.format("\n - %d Queued Aircraft Launches",#self.DefenseQueue)) -for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do -local DefenseQueueItem=DefenseQueueItem -Report:Add(string.format(" - %s - %s",DefenseQueueItem.SquadronName,DefenseQueueItem.DefenderSquadron.TakeoffTime,DefenseQueueItem.DefenderSquadron.TakeoffInterval)) -end -Report:Add(string.format("\n - Squadron Resources: ",#self.DefenseQueue)) -for DefenderSquadronName,DefenderSquadron in pairs(self.DefenderSquadrons)do -Report:Add(string.format(" - %s - %s",DefenderSquadronName,DefenderSquadron.ResourceCount and tostring(DefenderSquadron.ResourceCount)or"n/a")) -end -self:F(Report:Text("\n")) -trigger.action.outText(Report:Text("\n"),25) -end -return true -end -end -do -function AI_A2G_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem) -local DetectedSet=DetectedItem.Set -local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem) -local PlayerTypes={} -local PlayersCount=0 -if PlayersNearBy then -local DetectedThreatLevel=DetectedSet:CalculateThreatLevelA2G() -for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do -local PlayerUnit=PlayerUnitData -local PlayerName=PlayerUnit:GetPlayerName() -if PlayerUnit:IsAirPlane()and PlayerName~=nil then -local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel() -PlayersCount=PlayersCount+1 -local PlayerType=PlayerUnit:GetTypeName() -PlayerTypes[PlayerName]=PlayerType -if DetectedThreatLevel0 then -for PlayerName,PlayerType in pairs(PlayerTypes)do -PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType)) -end -else -PlayerTypesReport:Add("-") -end -return PlayersCount,PlayerTypesReport -end -function AI_A2G_DISPATCHER:GetFriendliesNearBy(DetectedItem) -local DetectedSet=DetectedItem.Set -local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem) -local FriendlyTypes={} -local FriendliesCount=0 -if FriendlyUnitsNearBy then -local DetectedThreatLevel=DetectedSet:CalculateThreatLevelA2G() -for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do -local FriendlyUnit=FriendlyUnitData -if FriendlyUnit:IsAirPlane()then -local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel() -FriendliesCount=FriendliesCount+1 -local FriendlyType=FriendlyUnit:GetTypeName() -FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1 -if DetectedThreatLevel0 then -for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do -FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType)) -end -else -FriendlyTypesReport:Add("-") -end -return FriendliesCount,FriendlyTypesReport -end -function AI_A2G_DISPATCHER:SchedulerPatrol(SquadronName) -local PatrolTaskTypes={"SEAD","CAS","BAI"} -local PatrolTaskType=PatrolTaskTypes[math.random(1,3)] -self:Patrol(SquadronName,PatrolTaskType) -end -function AI_A2G_DISPATCHER:SetSendMessages(onoff) -self.SetSendPlayerMessages=onoff -end -end -function AI_A2G_DISPATCHER:AddToSquadron(Squadron,Amount) -local Squadron=self:GetSquadron(Squadron) -if Squadron.ResourceCount then -Squadron.ResourceCount=Squadron.ResourceCount+Amount -end -self:T({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount}) -end -function AI_A2G_DISPATCHER:RemoveFromSquadron(Squadron,Amount) -local Squadron=self:GetSquadron(Squadron) -if Squadron.ResourceCount then -Squadron.ResourceCount=Squadron.ResourceCount-Amount -end -self:T({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount}) -end -AI_PATROL_ZONE={ -ClassName="AI_PATROL_ZONE", -} -function AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) -local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) -self.PatrolZone=PatrolZone -self.PatrolFloorAltitude=PatrolFloorAltitude -self.PatrolCeilingAltitude=PatrolCeilingAltitude -self.PatrolMinSpeed=PatrolMinSpeed -self.PatrolMaxSpeed=PatrolMaxSpeed -self.PatrolAltType=PatrolAltType or"BARO" -self:SetRefreshTimeInterval(30) -self.CheckStatus=true -self:ManageFuel(.2,60) -self:ManageDamage(1) -self.DetectedUnits={} -self:SetStartState("None") -self:AddTransition("*","Stop","Stopped") -self:AddTransition("None","Start","Patrolling") -self:AddTransition("Patrolling","Route","Patrolling") -self:AddTransition("*","Status","*") -self:AddTransition("*","Detect","*") -self:AddTransition("*","Detected","*") -self:AddTransition("*","RTB","Returning") -self:AddTransition("*","Reset","Patrolling") -self:AddTransition("*","Eject","*") -self:AddTransition("*","Crash","Crashed") -self:AddTransition("*","PilotDead","*") -return self -end -function AI_PATROL_ZONE:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed) -self:F2({PatrolMinSpeed,PatrolMaxSpeed}) -self.PatrolMinSpeed=PatrolMinSpeed -self.PatrolMaxSpeed=PatrolMaxSpeed -end -function AI_PATROL_ZONE:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude) -self:F2({PatrolFloorAltitude,PatrolCeilingAltitude}) -self.PatrolFloorAltitude=PatrolFloorAltitude -self.PatrolCeilingAltitude=PatrolCeilingAltitude -end -function AI_PATROL_ZONE:SetDetectionOn() -self:F2() -self.DetectOn=true -end -function AI_PATROL_ZONE:SetDetectionOff() -self:F2() -self.DetectOn=false -end -function AI_PATROL_ZONE:SetStatusOff() -self:F2() -self.CheckStatus=false -end -function AI_PATROL_ZONE:SetDetectionActivated() -self:F2() -self:ClearDetectedUnits() -self.DetectActivated=true -self:__Detect(-self.DetectInterval) -end -function AI_PATROL_ZONE:SetDetectionDeactivated() -self:F2() -self:ClearDetectedUnits() -self.DetectActivated=false -end -function AI_PATROL_ZONE:SetRefreshTimeInterval(Seconds) -self:F2() -if Seconds then -self.DetectInterval=Seconds -else -self.DetectInterval=30 -end -end -function AI_PATROL_ZONE:SetDetectionZone(DetectionZone) -self:F2() -if DetectionZone then -self.DetectZone=DetectionZone -else -self.DetectZone=nil -end -end -function AI_PATROL_ZONE:GetDetectedUnits() -self:F2() -return self.DetectedUnits -end -function AI_PATROL_ZONE:ClearDetectedUnits() -self:F2() -self.DetectedUnits={} -end -function AI_PATROL_ZONE:ManageFuel(PatrolFuelThresholdPercentage,PatrolOutOfFuelOrbitTime) -self.PatrolFuelThresholdPercentage=PatrolFuelThresholdPercentage -self.PatrolOutOfFuelOrbitTime=PatrolOutOfFuelOrbitTime -return self -end -function AI_PATROL_ZONE:ManageDamage(PatrolDamageThreshold) -self.PatrolManageDamage=true -self.PatrolDamageThreshold=PatrolDamageThreshold -return self -end -function AI_PATROL_ZONE:onafterStart(Controllable,From,Event,To) -self:F2() -self:__Route(1) -self:__Status(60) -self:SetDetectionActivated() -self:HandleEvent(EVENTS.PilotDead,self.OnPilotDead) -self:HandleEvent(EVENTS.Crash,self.OnCrash) -self:HandleEvent(EVENTS.Ejection,self.OnEjection) -Controllable:OptionROEHoldFire() -Controllable:OptionROTVertical() -self.Controllable:OnReSpawn( -function(PatrolGroup) -self:T("ReSpawn") -self:__Reset(1) -self:__Route(5) -end -) -self:SetDetectionOn() -end -function AI_PATROL_ZONE:onbeforeDetect(Controllable,From,Event,To) -return self.DetectOn and self.DetectActivated -end -function AI_PATROL_ZONE:onafterDetect(Controllable,From,Event,To) -local Detected=false -local DetectedTargets=Controllable:GetDetectedTargets() -for TargetID,Target in pairs(DetectedTargets or{})do -local TargetObject=Target.object -if TargetObject and TargetObject:isExist()and TargetObject.id_<50000000 then -local TargetUnit=UNIT:Find(TargetObject) -if TargetUnit and TargetUnit:IsAlive()then -local TargetUnitName=TargetUnit:GetName() -if self.DetectionZone then -if TargetUnit:IsInZone(self.DetectionZone)then -self:T({"Detected ",TargetUnit}) -if self.DetectedUnits[TargetUnit]==nil then -self.DetectedUnits[TargetUnit]=true -end -Detected=true -end -else -if self.DetectedUnits[TargetUnit]==nil then -self.DetectedUnits[TargetUnit]=true -end -Detected=true -end -end -end -end -self:__Detect(-self.DetectInterval) -if Detected==true then -self:__Detected(1.5) -end -end -function AI_PATROL_ZONE:_NewPatrolRoute(AIControllable) -local PatrolZone=AIControllable:GetState(AIControllable,"PatrolZone") -PatrolZone:__Route(1) -end -function AI_PATROL_ZONE:onafterRoute(Controllable,From,Event,To) -self:F2() -if From=="RTB"then -return -end -local life=self.Controllable:GetLife()or 0 -if self.Controllable:IsAlive()and life>1 then -local PatrolRoute={} -if self.Controllable:InAir()==false then -self:T("Not in the air, finding route path within PatrolZone") -local CurrentVec2=self.Controllable:GetVec2() -if not CurrentVec2 then return end -local CurrentAltitude=self.Controllable:GetAltitude() -local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) -local ToPatrolZoneSpeed=self.PatrolMaxSpeed -local CurrentRoutePoint=CurrentPointVec3:WaypointAir( -self.PatrolAltType, -POINT_VEC3.RoutePointType.TakeOffParking, -POINT_VEC3.RoutePointAction.FromParkingArea, -ToPatrolZoneSpeed, -true -) -PatrolRoute[#PatrolRoute+1]=CurrentRoutePoint -else -self:T("In the air, finding route path within PatrolZone") -local CurrentVec2=self.Controllable:GetVec2() -if not CurrentVec2 then return end -local CurrentAltitude=self.Controllable:GetAltitude() -local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) -local ToPatrolZoneSpeed=self.PatrolMaxSpeed -local CurrentRoutePoint=CurrentPointVec3:WaypointAir( -self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, -ToPatrolZoneSpeed, -true -) -PatrolRoute[#PatrolRoute+1]=CurrentRoutePoint -end -local ToTargetVec2=self.PatrolZone:GetRandomVec2() -self:T2(ToTargetVec2) -local ToTargetAltitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude) -local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) -self:T2({self.PatrolMinSpeed,self.PatrolMaxSpeed,ToTargetSpeed}) -local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y) -local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir( -self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, -ToTargetSpeed, -true -) -PatrolRoute[#PatrolRoute+1]=ToTargetRoutePoint -self.Controllable:WayPointInitialize(PatrolRoute) -self.Controllable:SetState(self.Controllable,"PatrolZone",self) -self.Controllable:WayPointFunction(#PatrolRoute,1,"AI_PATROL_ZONE:_NewPatrolRoute") -self.Controllable:WayPointExecute(1,2) -end -end -function AI_PATROL_ZONE:onbeforeStatus() -return self.CheckStatus -end -function AI_PATROL_ZONE:onafterStatus() -self:F2() -if self.Controllable and self.Controllable:IsAlive()then -local RTB=false -local Fuel=self.Controllable:GetFuelMin() -if Fuel Engaging') -self:__Engage(1) -end -end -end -function AI_CAP_ZONE:onafterAbort(Controllable,From,Event,To) -Controllable:ClearTasks() -self:__Route(1) -end -function AI_CAP_ZONE:onafterEngage(Controllable,From,Event,To) -if Controllable and Controllable:IsAlive()then -local EngageRoute={} -local CurrentVec2=self.Controllable:GetVec2() -if not CurrentVec2 then return self end -local CurrentAltitude=self.Controllable:GetAltitude() -local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) -local ToEngageZoneSpeed=self.PatrolMaxSpeed -local CurrentRoutePoint=CurrentPointVec3:WaypointAir( -self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, -ToEngageZoneSpeed, -true -) -EngageRoute[#EngageRoute+1]=CurrentRoutePoint -local ToTargetVec2=self.PatrolZone:GetRandomVec2() -self:T2(ToTargetVec2) -local ToTargetAltitude=math.random(self.EngageFloorAltitude,self.EngageCeilingAltitude) -local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) -self:T2({self.PatrolMinSpeed,self.PatrolMaxSpeed,ToTargetSpeed}) -local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y) -local ToPatrolRoutePoint=ToTargetPointVec3:WaypointAir( -self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, -ToTargetSpeed, -true -) -EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint -Controllable:OptionROEOpenFire() -Controllable:OptionROTEvadeFire() -local AttackTasks={} -for DetectedUnit,Detected in pairs(self.DetectedUnits)do -local DetectedUnit=DetectedUnit -self:T({DetectedUnit,DetectedUnit:IsAlive(),DetectedUnit:IsAir()}) -if DetectedUnit:IsAlive()and DetectedUnit:IsAir()then -if self.EngageZone then -if DetectedUnit:IsInZone(self.EngageZone)then -self:F({"Within Zone and Engaging ",DetectedUnit}) -AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit) -end -else -if self.EngageRange then -if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3())<=self.EngageRange then -self:F({"Within Range and Engaging",DetectedUnit}) -AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit) -end -else -AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit) -end -end -else -self.DetectedUnits[DetectedUnit]=nil -end -end -if#AttackTasks==0 then -self:F("No targets found -> Going back to Patrolling") -self:__Abort(1) -self:__Route(1) -self:SetDetectionActivated() -else -AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAP_ZONE.EngageRoute",self) -EngageRoute[1].task=Controllable:TaskCombo(AttackTasks) -self:SetDetectionDeactivated() -end -Controllable:Route(EngageRoute,0.5) -end -end -function AI_CAP_ZONE:onafterAccomplish(Controllable,From,Event,To) -self.Accomplished=true -self:SetDetectionOff() -end -function AI_CAP_ZONE:onafterDestroy(Controllable,From,Event,To,EventData) -if EventData.IniUnit then -self.DetectedUnits[EventData.IniUnit]=nil -end -end -function AI_CAP_ZONE:OnEventDead(EventData) -self:F({"EventDead",EventData}) -if EventData.IniDCSUnit then -if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then -self:__Destroy(1,EventData) -end -end -end -AI_CAS_ZONE={ -ClassName="AI_CAS_ZONE", -} -function AI_CAS_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageZone,PatrolAltType) -local self=BASE:Inherit(self,AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)) -self.EngageZone=EngageZone -self.Accomplished=false -self:SetDetectionZone(self.EngageZone) -self:AddTransition({"Patrolling","Engaging"},"Engage","Engaging") -self:AddTransition("Engaging","Target","Engaging") -self:AddTransition("Engaging","Fired","Engaging") -self:AddTransition("*","Destroy","*") -self:AddTransition("Engaging","Abort","Patrolling") -self:AddTransition("Engaging","Accomplish","Patrolling") -return self -end -function AI_CAS_ZONE:SetEngageZone(EngageZone) -self:F2() -if EngageZone then -self.EngageZone=EngageZone -else -self.EngageZone=nil -end -end -function AI_CAS_ZONE:onafterStart(Controllable,From,Event,To) -self:GetParent(self).onafterStart(self,Controllable,From,Event,To) -self:HandleEvent(EVENTS.Dead) -self:SetDetectionDeactivated() -end -function AI_CAS_ZONE.EngageRoute(EngageGroup,Fsm) -EngageGroup:F({"AI_CAS_ZONE.EngageRoute:",EngageGroup:GetName()}) -if EngageGroup:IsAlive()then -Fsm:__Engage(1,Fsm.EngageSpeed,Fsm.EngageAltitude,Fsm.EngageWeaponExpend,Fsm.EngageAttackQty,Fsm.EngageDirection) -end -end -function AI_CAS_ZONE:onbeforeEngage(Controllable,From,Event,To) -if self.Accomplished==true then -return false -end -end -function AI_CAS_ZONE:onafterTarget(Controllable,From,Event,To) -if Controllable:IsAlive()then -local AttackTasks={} -for DetectedUnit,Detected in pairs(self.DetectedUnits)do -local DetectedUnit=DetectedUnit -if DetectedUnit:IsAlive()then -if DetectedUnit:IsInZone(self.EngageZone)then -if Detected==true then -self:F({"Target: ",DetectedUnit}) -self.DetectedUnits[DetectedUnit]=false -local AttackTask=Controllable:TaskAttackUnit(DetectedUnit,false,self.EngageWeaponExpend,self.EngageAttackQty,self.EngageDirection,self.EngageAltitude,nil) -self.Controllable:PushTask(AttackTask,1) -end -end -else -self.DetectedUnits[DetectedUnit]=nil -end -end -self:__Target(-10) -end -end -function AI_CAS_ZONE:onafterAbort(Controllable,From,Event,To) -Controllable:ClearTasks() -self:__Route(1) -end -function AI_CAS_ZONE:onafterEngage(Controllable,From,Event,To, -EngageSpeed, -EngageAltitude, -EngageWeaponExpend, -EngageAttackQty, -EngageDirection) -self:F("onafterEngage") -self.EngageSpeed=EngageSpeed or 400 -self.EngageAltitude=EngageAltitude or 2000 -self.EngageWeaponExpend=EngageWeaponExpend -self.EngageAttackQty=EngageAttackQty -self.EngageDirection=EngageDirection -if Controllable:IsAlive()then -Controllable:OptionROEOpenFire() -Controllable:OptionROTVertical() -local EngageRoute={} -local CurrentVec2=self.Controllable:GetVec2() -local CurrentAltitude=self.Controllable:GetAltitude() -local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) -local ToEngageZoneSpeed=self.PatrolMaxSpeed -local CurrentRoutePoint=CurrentPointVec3:WaypointAir( -self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, -self.EngageSpeed, -true -) -EngageRoute[#EngageRoute+1]=CurrentRoutePoint -local AttackTasks={} -for DetectedUnit,Detected in pairs(self.DetectedUnits)do -local DetectedUnit=DetectedUnit -self:T(DetectedUnit) -if DetectedUnit:IsAlive()then -if DetectedUnit:IsInZone(self.EngageZone)then -self:F({"Engaging ",DetectedUnit}) -AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit, -true, -EngageWeaponExpend, -EngageAttackQty, -EngageDirection -) -end -else -self.DetectedUnits[DetectedUnit]=nil -end -end -AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAS_ZONE.EngageRoute",self) -EngageRoute[#EngageRoute].task=Controllable:TaskCombo(AttackTasks) -local ToTargetVec2=self.EngageZone:GetRandomVec2() -self:T2(ToTargetVec2) -local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y) -local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir( -self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, -self.EngageSpeed, -true -) -EngageRoute[#EngageRoute+1]=ToTargetRoutePoint -Controllable:Route(EngageRoute,0.5) -self:SetRefreshTimeInterval(2) -self:SetDetectionActivated() -self:__Target(-2) -end -end -function AI_CAS_ZONE:onafterAccomplish(Controllable,From,Event,To) -self.Accomplished=true -self:SetDetectionDeactivated() -end -function AI_CAS_ZONE:onafterDestroy(Controllable,From,Event,To,EventData) -if EventData.IniUnit then -self.DetectedUnits[EventData.IniUnit]=nil -end -end -function AI_CAS_ZONE:OnEventDead(EventData) -self:F({"EventDead",EventData}) -if EventData.IniDCSUnit then -if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then -self:__Destroy(1,EventData) -end -end -end -AI_BAI_ZONE={ -ClassName="AI_BAI_ZONE", -} -function AI_BAI_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageZone,PatrolAltType) -local self=BASE:Inherit(self,AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)) -self.EngageZone=EngageZone -self.Accomplished=false -self:SetDetectionZone(self.EngageZone) -self:SearchOn() -self:AddTransition({"Patrolling","Engaging"},"Engage","Engaging") -self:AddTransition("Engaging","Target","Engaging") -self:AddTransition("Engaging","Fired","Engaging") -self:AddTransition("*","Destroy","*") -self:AddTransition("Engaging","Abort","Patrolling") -self:AddTransition("Engaging","Accomplish","Patrolling") -return self -end -function AI_BAI_ZONE:SetEngageZone(EngageZone) -self:F2() -if EngageZone then -self.EngageZone=EngageZone -else -self.EngageZone=nil -end -end -function AI_BAI_ZONE:SearchOnOff(Search) -self.Search=Search -return self -end -function AI_BAI_ZONE:SearchOff() -self:SearchOnOff(false) -return self -end -function AI_BAI_ZONE:SearchOn() -self:SearchOnOff(true) -return self -end -function AI_BAI_ZONE:onafterStart(Controllable,From,Event,To) -self:GetParent(self).onafterStart(self,Controllable,From,Event,To) -self:HandleEvent(EVENTS.Dead) -self:SetDetectionDeactivated() -end -function _NewEngageRoute(AIControllable) -AIControllable:T("NewEngageRoute") -local EngageZone=AIControllable:GetState(AIControllable,"EngageZone") -EngageZone:__Engage(1,EngageZone.EngageSpeed,EngageZone.EngageAltitude,EngageZone.EngageWeaponExpend,EngageZone.EngageAttackQty,EngageZone.EngageDirection) -end -function AI_BAI_ZONE:onbeforeEngage(Controllable,From,Event,To) -if self.Accomplished==true then -return false -end -end -function AI_BAI_ZONE:onafterTarget(Controllable,From,Event,To) -self:F({"onafterTarget",self.Search,Controllable:IsAlive()}) -if Controllable:IsAlive()then -local AttackTasks={} -if self.Search==true then -for DetectedUnit,Detected in pairs(self.DetectedUnits)do -local DetectedUnit=DetectedUnit -if DetectedUnit:IsAlive()then -if DetectedUnit:IsInZone(self.EngageZone)then -if Detected==true then -self:F({"Target: ",DetectedUnit}) -self.DetectedUnits[DetectedUnit]=false -local AttackTask=Controllable:TaskAttackUnit(DetectedUnit,false,self.EngageWeaponExpend,self.EngageAttackQty,self.EngageDirection,self.EngageAltitude,nil) -self.Controllable:PushTask(AttackTask,1) -end -end -else -self.DetectedUnits[DetectedUnit]=nil -end -end -else -self:F("Attack zone") -local AttackTask=Controllable:TaskAttackMapObject( -self.EngageZone:GetPointVec2():GetVec2(), -true, -self.EngageWeaponExpend, -self.EngageAttackQty, -self.EngageDirection, -self.EngageAltitude -) -self.Controllable:PushTask(AttackTask,1) -end -self:__Target(-10) -end -end -function AI_BAI_ZONE:onafterAbort(Controllable,From,Event,To) -Controllable:ClearTasks() -self:__Route(1) -end -function AI_BAI_ZONE:onafterEngage(Controllable,From,Event,To, -EngageSpeed, -EngageAltitude, -EngageWeaponExpend, -EngageAttackQty, -EngageDirection) -self:F("onafterEngage") -self.EngageSpeed=EngageSpeed or 400 -self.EngageAltitude=EngageAltitude or 2000 -self.EngageWeaponExpend=EngageWeaponExpend -self.EngageAttackQty=EngageAttackQty -self.EngageDirection=EngageDirection -if Controllable:IsAlive()then -local EngageRoute={} -local CurrentVec2=self.Controllable:GetVec2() -local CurrentAltitude=self.Controllable:GetAltitude() -local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) -local ToEngageZoneSpeed=self.PatrolMaxSpeed -local CurrentRoutePoint=CurrentPointVec3:WaypointAir( -self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, -self.EngageSpeed, -true -) -EngageRoute[#EngageRoute+1]=CurrentRoutePoint -local AttackTasks={} -if self.Search==true then -for DetectedUnitID,DetectedUnitData in pairs(self.DetectedUnits)do -local DetectedUnit=DetectedUnitData -self:T(DetectedUnit) -if DetectedUnit:IsAlive()then -if DetectedUnit:IsInZone(self.EngageZone)then -self:F({"Engaging ",DetectedUnit}) -AttackTasks[#AttackTasks+1]=Controllable:TaskBombing( -DetectedUnit:GetPointVec2():GetVec2(), -true, -EngageWeaponExpend, -EngageAttackQty, -EngageDirection, -EngageAltitude -) -end -else -self.DetectedUnits[DetectedUnit]=nil -end -end -else -self:F("Attack zone") -AttackTasks[#AttackTasks+1]=Controllable:TaskAttackMapObject( -self.EngageZone:GetPointVec2():GetVec2(), -true, -EngageWeaponExpend, -EngageAttackQty, -EngageDirection, -EngageAltitude -) -end -EngageRoute[#EngageRoute].task=Controllable:TaskCombo(AttackTasks) -local ToTargetVec2=self.EngageZone:GetRandomVec2() -self:T2(ToTargetVec2) -local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y) -local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir( -self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, -self.EngageSpeed, -true -) -EngageRoute[#EngageRoute+1]=ToTargetRoutePoint -Controllable:OptionROEOpenFire() -Controllable:OptionROTVertical() -Controllable:WayPointInitialize(EngageRoute) -Controllable:SetState(Controllable,"EngageZone",self) -Controllable:WayPointFunction(#EngageRoute,1,"_NewEngageRoute") -Controllable:WayPointExecute(1) -self:SetRefreshTimeInterval(2) -self:SetDetectionActivated() -self:__Target(-2) -end -end -function AI_BAI_ZONE:onafterAccomplish(Controllable,From,Event,To) -self.Accomplished=true -self:SetDetectionDeactivated() -end -function AI_BAI_ZONE:onafterDestroy(Controllable,From,Event,To,EventData) -if EventData.IniUnit then -self.DetectedUnits[EventData.IniUnit]=nil -end -end -function AI_BAI_ZONE:OnEventDead(EventData) -self:F({"EventDead",EventData}) -if EventData.IniDCSUnit then -if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then -self:__Destroy(1,EventData) -end -end -end -AI_FORMATION={ -ClassName="AI_FORMATION", -FollowName=nil, -FollowUnit=nil, -FollowGroupSet=nil, -FollowMode=1, -MODE={ -FOLLOW=1, -MISSION=2, -}, -FollowScheduler=nil, -OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE, -OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, -dtFollow=0.5, -} -AI_FORMATION.__Enum={} -AI_FORMATION.__Enum.Formation={ -None=0, -Mission=1, -Line=2, -Trail=3, -Stack=4, -LeftLine=5, -RightLine=6, -LeftWing=7, -RightWing=8, -Vic=9, -Box=10, -} -AI_FORMATION.__Enum.Mode={ -Mission="M", -Formation="F", -Attack="A", -Reconnaissance="R", -} -AI_FORMATION.__Enum.ReportType={ -Airborne="*", -Airborne="A", -GroundRadar="R", -Ground="G", -} -function AI_FORMATION:New(FollowUnit,FollowGroupSet,FollowName,FollowBriefing) -local self=BASE:Inherit(self,FSM_SET:New(FollowGroupSet)) -self:F({FollowUnit,FollowGroupSet,FollowName}) -self.FollowUnit=FollowUnit -self.FollowGroupSet=FollowGroupSet -self.FollowGroupSet:ForEachGroup( -function(FollowGroup) -FollowGroup:SetState(self,"Mode",self.__Enum.Mode.Formation) -end -) -self:SetFlightModeFormation() -self:SetFlightRandomization(2) -self:SetStartState("None") -self:AddTransition("*","Stop","Stopped") -self:AddTransition({"None","Stopped"},"Start","Following") -self:AddTransition("*","FormationLine","*") -self:AddTransition("*","FormationTrail","*") -self:AddTransition("*","FormationStack","*") -self:AddTransition("*","FormationLeftLine","*") -self:AddTransition("*","FormationRightLine","*") -self:AddTransition("*","FormationLeftWing","*") -self:AddTransition("*","FormationRightWing","*") -self:AddTransition("*","FormationCenterWing","*") -self:AddTransition("*","FormationVic","*") -self:AddTransition("*","FormationBox","*") -self:AddTransition("*","Follow","Following") -self:FormationLeftLine(500,0,250,250) -self.FollowName=FollowName -self.FollowBriefing=FollowBriefing -self.CT1=0 -self.GT1=0 -self.FollowMode=AI_FORMATION.MODE.MISSION -return self -end -function AI_FORMATION:SetFollowTimeInterval(dt) -self.dtFollow=dt or 0.5 -return self -end -function AI_FORMATION:TestSmokeDirectionVector(SmokeDirection) -self.SmokeDirectionVector=(SmokeDirection==true)and true or false -return self -end -function AI_FORMATION:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,Formation) -self:F({FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,Formation}) -XStart=XStart or self.XStart -XSpace=XSpace or self.XSpace -YStart=YStart or self.YStart -YSpace=YSpace or self.YSpace -ZStart=ZStart or self.ZStart -ZSpace=ZSpace or self.ZSpace -FollowGroupSet:Flush(self) -local FollowSet=FollowGroupSet:GetSet() -local i=1 -for FollowID,FollowGroup in pairs(FollowSet)do -local PointVec3=POINT_VEC3:New() -PointVec3:SetX(XStart+i*XSpace) -PointVec3:SetY(YStart+i*YSpace) -PointVec3:SetZ(ZStart+i*ZSpace) -local Vec3=PointVec3:GetVec3() -FollowGroup:SetState(self,"FormationVec3",Vec3) -i=i+1 -FollowGroup:SetState(FollowGroup,"Formation",Formation) -end -return self -end -function AI_FORMATION:onafterFormationTrail(FollowGroupSet,From,Event,To,XStart,XSpace,YStart) -self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,0,0,self.__Enum.Formation.Trail) -return self -end -function AI_FORMATION:onafterFormationStack(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace) -self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,0,0,self.__Enum.Formation.Stack) -return self -end -function AI_FORMATION:onafterFormationLeftLine(FollowGroupSet,From,Event,To,XStart,YStart,ZStart,ZSpace) -self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,-ZStart,-ZSpace,self.__Enum.Formation.LeftLine) -return self -end -function AI_FORMATION:onafterFormationRightLine(FollowGroupSet,From,Event,To,XStart,YStart,ZStart,ZSpace) -self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightLine) -return self -end -function AI_FORMATION:onafterFormationLeftWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,ZStart,ZSpace) -self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,-ZStart,-ZSpace,self.__Enum.Formation.LeftWing) -return self -end -function AI_FORMATION:onafterFormationRightWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,ZStart,ZSpace) -self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightWing) -return self -end -function AI_FORMATION:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace) -local FollowSet=FollowGroupSet:GetSet() -local i=0 -for FollowID,FollowGroup in pairs(FollowSet)do -local PointVec3=POINT_VEC3:New() -local Side=(i%2==0)and 1 or-1 -local Row=i/2+1 -PointVec3:SetX(XStart+Row*XSpace) -PointVec3:SetY(YStart) -PointVec3:SetZ(Side*(ZStart+i*ZSpace)) -local Vec3=PointVec3:GetVec3() -FollowGroup:SetState(self,"FormationVec3",Vec3) -i=i+1 -FollowGroup:SetState(FollowGroup,"Formation",self.__Enum.Formation.Vic) -end -return self -end -function AI_FORMATION:onafterFormationVic(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace) -self:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace) -return self -end -function AI_FORMATION:onafterFormationBox(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) -local FollowSet=FollowGroupSet:GetSet() -local i=0 -for FollowID,FollowGroup in pairs(FollowSet)do -local PointVec3=POINT_VEC3:New() -local ZIndex=i%ZLevels -local XIndex=math.floor(i/ZLevels) -local YIndex=math.floor(i/ZLevels) -PointVec3:SetX(XStart+XIndex*XSpace) -PointVec3:SetY(YStart+YIndex*YSpace) -PointVec3:SetZ(-ZStart-(ZSpace*ZLevels/2)+ZSpace*ZIndex) -local Vec3=PointVec3:GetVec3() -FollowGroup:SetState(self,"FormationVec3",Vec3) -i=i+1 -FollowGroup:SetState(FollowGroup,"Formation",self.__Enum.Formation.Box) -end -return self -end -function AI_FORMATION:SetFlightRandomization(FlightRandomization) -self.FlightRandomization=FlightRandomization -return self -end -function AI_FORMATION:GetFlightMode(FollowGroup) -if FollowGroup then -FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) -FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Mission) -end -return FollowGroup:GetState(FollowGroup,"Mode") -end -function AI_FORMATION:SetFlightModeMission(FollowGroup) -if FollowGroup then -FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) -FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Mission) -else -self.FollowGroupSet:ForSomeGroupAlive( -function(FollowGroup) -FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) -FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Mission) -end -) -end -return self -end -function AI_FORMATION:SetFlightModeAttack(FollowGroup) -if FollowGroup then -FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) -FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Attack) -else -self.FollowGroupSet:ForSomeGroupAlive( -function(FollowGroup) -FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) -FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Attack) -end -) -end -return self -end -function AI_FORMATION:SetFlightModeFormation(FollowGroup) -if FollowGroup then -FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) -FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Formation) -else -self.FollowGroupSet:ForSomeGroupAlive( -function(FollowGroup) -FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) -FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Formation) -end -) -end -return self -end -function AI_FORMATION:onafterStop(FollowGroupSet,From,Event,To) -self:E("Stopping formation.") -end -function AI_FORMATION:onbeforeFollow(FollowGroupSet,From,Event,To) -if From=="Stopped"then -return false -end -return true -end -function AI_FORMATION:onenterFollowing(FollowGroupSet) -if self.FollowUnit:IsAlive()then -local ClientUnit=self.FollowUnit -local CT1,CT2,CV1,CV2 -CT1=ClientUnit:GetState(self,"CT1") -local CuVec3=ClientUnit:GetVec3() -if CT1==nil or CT1==0 then -ClientUnit:SetState(self,"CV1",CuVec3) -ClientUnit:SetState(self,"CT1",timer.getTime()) -else -CT1=ClientUnit:GetState(self,"CT1") -CT2=timer.getTime() -CV1=ClientUnit:GetState(self,"CV1") -CV2=CuVec3 -ClientUnit:SetState(self,"CT1",CT2) -ClientUnit:SetState(self,"CV1",CV2) -end -for _,_group in pairs(FollowGroupSet:GetSet())do -local group=_group -if group and group:IsAlive()then -self:FollowMe(group,ClientUnit,CT1,CV1,CT2,CV2) -end -end -self:__Follow(-self.dtFollow) -end -end -function AI_FORMATION:FollowMe(FollowGroup,ClientUnit,CT1,CV1,CT2,CV2) -if FollowGroup:GetState(FollowGroup,"Mode")==self.__Enum.Mode.Formation and not self:Is("Stopped")then -self:T({Mode=FollowGroup:GetState(FollowGroup,"Mode")}) -FollowGroup:OptionROTEvadeFire() -FollowGroup:OptionROEReturnFire() -local GroupUnit=FollowGroup:GetUnit(1) -local GuVec3=GroupUnit:GetVec3() -local FollowFormation=FollowGroup:GetState(self,"FormationVec3") -if FollowFormation then -local FollowDistance=FollowFormation.x -local GT1=GroupUnit:GetState(self,"GT1") -if CT1==nil or CT1==0 or GT1==nil or GT1==0 then -GroupUnit:SetState(self,"GV1",GuVec3) -GroupUnit:SetState(self,"GT1",timer.getTime()) -else -local CD=((CV2.x-CV1.x)^2+(CV2.y-CV1.y)^2+(CV2.z-CV1.z)^2)^0.5 -local CT=CT2-CT1 -local CS=(3600/CT)*(CD/1000)/3.6 -local CDv={x=CV2.x-CV1.x,y=CV2.y-CV1.y,z=CV2.z-CV1.z} -local Ca=math.atan2(CDv.x,CDv.z) -local GT1=GroupUnit:GetState(self,"GT1") -local GT2=timer.getTime() -local GV1=GroupUnit:GetState(self,"GV1") -local GV2=GuVec3 -GV2.x=GV2.x+math.random(-self.FlightRandomization/2,self.FlightRandomization/2) -GV2.y=GV2.y+math.random(-self.FlightRandomization/2,self.FlightRandomization/2) -GV2.z=GV2.z+math.random(-self.FlightRandomization/2,self.FlightRandomization/2) -GroupUnit:SetState(self,"GT1",GT2) -GroupUnit:SetState(self,"GV1",GV2) -local GD=((GV2.x-GV1.x)^2+(GV2.y-GV1.y)^2+(GV2.z-GV1.z)^2)^0.5 -local GT=GT2-GT1 -local GDv={x=GV2.x-CV1.x,y=GV2.y-CV1.y,z=GV2.z-CV1.z} -local Alpha_T=math.atan2(GDv.x,GDv.z)-math.atan2(CDv.x,CDv.z) -local Alpha_R=(Alpha_T<0)and Alpha_T+2*math.pi or Alpha_T -local Position=math.cos(Alpha_R) -local GD=((GDv.x)^2+(GDv.z)^2)^0.5 -local Distance=GD*Position+-CS*0.5 -local GV={x=GV2.x-CV2.x,y=GV2.y-CV2.y,z=GV2.z-CV2.z} -local GH2={x=GV2.x,y=CV2.y+FollowFormation.y,z=GV2.z} -local alpha=math.atan2(GV.x,GV.z) -local GVx=FollowFormation.z*math.cos(Ca)+FollowFormation.x*math.sin(Ca) -local GVz=FollowFormation.x*math.cos(Ca)-FollowFormation.z*math.sin(Ca) -local Inclination=(Distance+FollowFormation.x)/10 -if Inclination<-30 then -Inclination=-30 -end -local CVI={ -x=CV2.x+CS*10*math.sin(Ca), -y=GH2.y+Inclination, -y=GH2.y, -z=CV2.z+CS*10*math.cos(Ca), -} -local DV={x=CV2.x-CVI.x,y=CV2.y-CVI.y,z=CV2.z-CVI.z} -local DVu={x=DV.x/FollowDistance,y=DV.y,z=DV.z/FollowDistance} -local GDV={x=CVI.x,y=CVI.y,z=CVI.z} -local ADDx=FollowFormation.x*math.cos(alpha)-FollowFormation.z*math.sin(alpha) -local ADDz=FollowFormation.z*math.cos(alpha)+FollowFormation.x*math.sin(alpha) -local GDV_Formation={ -x=GDV.x-GVx, -y=GDV.y, -z=GDV.z-GVz -} -if self.SmokeDirectionVector==true then -trigger.action.smoke(GDV,trigger.smokeColor.Green) -trigger.action.smoke(GDV_Formation,trigger.smokeColor.White) -end -local Time=120 -local Speed=-(Distance+FollowFormation.x)/Time -if Distance>-10000 then -Speed=-(Distance+FollowFormation.x)/60 -end -if Distance>-2500 then -Speed=-(Distance+FollowFormation.x)/20 -end -local GS=Speed+CS -FollowGroup:RouteToVec3(GDV_Formation,GS) -end -end -end -end -AI_ESCORT={ -ClassName="AI_ESCORT", -EscortName=nil, -EscortUnit=nil, -EscortGroup=nil, -EscortMode=1, -Targets={}, -FollowScheduler=nil, -ReportTargets=true, -OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE, -OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, -SmokeDirectionVector=false, -TaskPoints={} -} -AI_ESCORT.Detection=nil -function AI_ESCORT:New(EscortUnit,EscortGroupSet,EscortName,EscortBriefing) -local self=BASE:Inherit(self,AI_FORMATION:New(EscortUnit,EscortGroupSet,EscortName,EscortBriefing)) -self:F({EscortUnit,EscortGroupSet}) -self.PlayerUnit=self.FollowUnit -self.PlayerGroup=self.FollowUnit:GetGroup() -self.EscortName=EscortName -self.EscortGroupSet=EscortGroupSet -self.EscortGroupSet:SetSomeIteratorLimit(8) -self.EscortBriefing=EscortBriefing -self.Menu={} -self.Menu.HoldAtEscortPosition=self.Menu.HoldAtEscortPosition or{} -self.Menu.HoldAtLeaderPosition=self.Menu.HoldAtLeaderPosition or{} -self.Menu.Flare=self.Menu.Flare or{} -self.Menu.Smoke=self.Menu.Smoke or{} -self.Menu.Targets=self.Menu.Targets or{} -self.Menu.ROE=self.Menu.ROE or{} -self.Menu.ROT=self.Menu.ROT or{} -self.FollowDistance=100 -self.CT1=0 -self.GT1=0 -EscortGroupSet:ForEachGroup( -function(EscortGroup) -if not self.PlayerUnit._EscortGroups then -self.PlayerUnit._EscortGroups={} -end -if not self.PlayerUnit._EscortGroups[EscortGroup:GetName()]then -self.PlayerUnit._EscortGroups[EscortGroup:GetName()]={} -self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortGroup=EscortGroup -self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortName=self.EscortName -self.PlayerUnit._EscortGroups[EscortGroup:GetName()].Detection=self.Detection -end -end -) -self:SetFlightReportType(self.__Enum.ReportType.All) -return self -end -function AI_ESCORT:_InitFlightMenus() -self:SetFlightMenuJoinUp() -self:SetFlightMenuFormation("Trail") -self:SetFlightMenuFormation("Stack") -self:SetFlightMenuFormation("LeftLine") -self:SetFlightMenuFormation("RightLine") -self:SetFlightMenuFormation("LeftWing") -self:SetFlightMenuFormation("RightWing") -self:SetFlightMenuFormation("Vic") -self:SetFlightMenuFormation("Box") -self:SetFlightMenuHoldAtEscortPosition() -self:SetFlightMenuHoldAtLeaderPosition() -self:SetFlightMenuFlare() -self:SetFlightMenuSmoke() -self:SetFlightMenuROE() -self:SetFlightMenuROT() -self:SetFlightMenuTargets() -self:SetFlightMenuReportType() -end -function AI_ESCORT:_InitEscortMenus(EscortGroup) -EscortGroup.EscortMenu=MENU_GROUP:New(self.PlayerGroup,EscortGroup:GetCallsign(),self.MainMenu) -self:SetEscortMenuJoinUp(EscortGroup) -self:SetEscortMenuResumeMission(EscortGroup) -self:SetEscortMenuHoldAtEscortPosition(EscortGroup) -self:SetEscortMenuHoldAtLeaderPosition(EscortGroup) -self:SetEscortMenuFlare(EscortGroup) -self:SetEscortMenuSmoke(EscortGroup) -self:SetEscortMenuROE(EscortGroup) -self:SetEscortMenuROT(EscortGroup) -self:SetEscortMenuTargets(EscortGroup) -end -function AI_ESCORT:_InitEscortRoute(EscortGroup) -EscortGroup.MissionRoute=EscortGroup:GetTaskRoute() -end -function AI_ESCORT:onafterStart(EscortGroupSet) -self:F() -EscortGroupSet:ForEachGroup( -function(EscortGroup) -EscortGroup:WayPointInitialize() -EscortGroup:OptionROTVertical() -EscortGroup:OptionROEOpenFire() -end -) -local LeaderEscort=EscortGroupSet:GetFirst() -if LeaderEscort then -local Report=REPORT:New("Escort reporting:") -Report:Add("Joining Up "..EscortGroupSet:GetUnitTypeNames():Text(", ").." from "..LeaderEscort:GetCoordinate():ToString(self.PlayerUnit)) -LeaderEscort:MessageTypeToGroup(Report:Text(),MESSAGE.Type.Information,self.PlayerUnit) -end -self.Detection=DETECTION_AREAS:New(EscortGroupSet,5000) -self.Detection:InitDetectVisual(true) -self.Detection:InitDetectIRST(true) -self.Detection:InitDetectOptical(true) -self.Detection:InitDetectRadar(true) -self.Detection:InitDetectRWR(true) -self.Detection:SetAcceptRange(100000) -self.Detection:__Start(30) -self.MainMenu=MENU_GROUP:New(self.PlayerGroup,self.EscortName) -self.FlightMenu=MENU_GROUP:New(self.PlayerGroup,"Flight",self.MainMenu) -self:_InitFlightMenus() -self.EscortGroupSet:ForSomeGroupAlive( -function(EscortGroup) -self:_InitEscortMenus(EscortGroup) -self:_InitEscortRoute(EscortGroup) -self:SetFlightModeFormation(EscortGroup) -function EscortGroup:OnEventDeadOrCrash(EventData) -self:F({"EventDead",EventData}) -self.EscortMenu:Remove() -end -EscortGroup:HandleEvent(EVENTS.Dead,EscortGroup.OnEventDeadOrCrash) -EscortGroup:HandleEvent(EVENTS.Crash,EscortGroup.OnEventDeadOrCrash) -end -) -end -function AI_ESCORT:onafterStop(EscortGroupSet) -self:F() -EscortGroupSet:ForEachGroup( -function(EscortGroup) -EscortGroup:WayPointInitialize() -EscortGroup:OptionROTVertical() -EscortGroup:OptionROEOpenFire() -end -) -self.Detection:Stop() -self.MainMenu:Remove() -end -function AI_ESCORT:SetDetection(Detection) -self.Detection=Detection -self.EscortGroup.Detection=self.Detection -self.PlayerUnit._EscortGroups[self.EscortGroup:GetName()].Detection=self.EscortGroup.Detection -Detection:__Start(1) -end -function AI_ESCORT:TestSmokeDirectionVector(SmokeDirection) -self.SmokeDirectionVector=(SmokeDirection==true)and true or false -end -function AI_ESCORT:MenusHelicopters(XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) -self:F() -self.XStart=XStart or 50 -self.XSpace=XSpace or 50 -self.YStart=YStart or 50 -self.YSpace=YSpace or 50 -self.ZStart=ZStart or 50 -self.ZSpace=ZSpace or 50 -self.ZLevels=ZLevels or 10 -self:MenuJoinUp() -self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart) -self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace) -self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) -self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) -self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) -self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) -self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace) -self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels) -self:MenuHoldAtEscortPosition(30) -self:MenuHoldAtEscortPosition(100) -self:MenuHoldAtEscortPosition(500) -self:MenuHoldAtLeaderPosition(30,500) -self:MenuFlare() -self:MenuSmoke() -self:MenuTargets(60) -self:MenuAssistedAttack() -self:MenuROE() -self:MenuROT() -return self -end -function AI_ESCORT:MenusAirplanes(XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) -self:F() -self.XStart=XStart or 50 -self.XSpace=XSpace or 50 -self.YStart=YStart or 50 -self.YSpace=YSpace or 50 -self.ZStart=ZStart or 50 -self.ZSpace=ZSpace or 50 -self.ZLevels=ZLevels or 10 -self:MenuJoinUp() -self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart) -self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace) -self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) -self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) -self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) -self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) -self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace) -self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels) -self:MenuHoldAtEscortPosition(1000,500) -self:MenuHoldAtLeaderPosition(1000,500) -self:MenuFlare() -self:MenuSmoke() -self:MenuTargets(60) -self:MenuAssistedAttack() -self:MenuROE() -self:MenuROT() -return self -end -function AI_ESCORT:SetFlightMenuFormation(Formation) -local FormationID="Formation"..Formation -local MenuFormation=self.Menu[FormationID] -if MenuFormation then -local Arguments=MenuFormation.Arguments -local FlightMenuFormation=MENU_GROUP:New(self.PlayerGroup,"Formation",self.MainMenu) -local MenuFlightFormationID=MENU_GROUP_COMMAND:New(self.PlayerGroup,Formation,FlightMenuFormation, -function(self,Formation,...) -self.EscortGroupSet:ForSomeGroupAlive( -function(EscortGroup,self,Formation,Arguments) -if EscortGroup:IsAir()then -self:E({FormationID=FormationID}) -self[FormationID](self,unpack(Arguments)) -end -end,self,Formation,Arguments -) -end,self,Formation,Arguments -) -end -return self -end -function AI_ESCORT:MenuFormation(Formation,...) -local FormationID="Formation"..Formation -self.Menu[FormationID]=self.Menu[FormationID]or{} -self.Menu[FormationID].Arguments=arg -end -function AI_ESCORT:MenuFormationTrail(XStart,XSpace,YStart) -self:MenuFormation("Trail",XStart,XSpace,YStart) -return self -end -function AI_ESCORT:MenuFormationStack(XStart,XSpace,YStart,YSpace) -self:MenuFormation("Stack",XStart,XSpace,YStart,YSpace) -return self -end -function AI_ESCORT:MenuFormationLeftLine(XStart,YStart,ZStart,ZSpace) -self:MenuFormation("LeftLine",XStart,YStart,ZStart,ZSpace) -return self -end -function AI_ESCORT:MenuFormationRightLine(XStart,YStart,ZStart,ZSpace) -self:MenuFormation("RightLine",XStart,YStart,ZStart,ZSpace) -return self -end -function AI_ESCORT:MenuFormationLeftWing(XStart,XSpace,YStart,ZStart,ZSpace) -self:MenuFormation("LeftWing",XStart,XSpace,YStart,ZStart,ZSpace) -return self -end -function AI_ESCORT:MenuFormationRightWing(XStart,XSpace,YStart,ZStart,ZSpace) -self:MenuFormation("RightWing",XStart,XSpace,YStart,ZStart,ZSpace) -return self -end -function AI_ESCORT:MenuFormationCenterWing(XStart,XSpace,YStart,YSpace,ZStart,ZSpace) -self:MenuFormation("CenterWing",XStart,XSpace,YStart,YSpace,ZStart,ZSpace) -return self -end -function AI_ESCORT:MenuFormationVic(XStart,XSpace,YStart,YSpace,ZStart,ZSpace) -self:MenuFormation("Vic",XStart,XSpace,YStart,YSpace,ZStart,ZSpace) -return self -end -function AI_ESCORT:MenuFormationBox(XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) -self:MenuFormation("Box",XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) -return self -end -function AI_ESCORT:SetFlightMenuJoinUp() -if self.Menu.JoinUp==true then -local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) -local FlightMenuJoinUp=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Join Up",FlightMenuReportNavigation,AI_ESCORT._FlightJoinUp,self) -end -end -function AI_ESCORT:SetEscortMenuJoinUp(EscortGroup) -if self.Menu.JoinUp==true then -if EscortGroup:IsAir()then -local EscortGroupName=EscortGroup:GetName() -local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) -local EscortMenuJoinUp=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Join Up",EscortMenuReportNavigation,AI_ESCORT._JoinUp,self,EscortGroup) -end -end -end -function AI_ESCORT:MenuJoinUp() -self.Menu.JoinUp=true -return self -end -function AI_ESCORT:SetFlightMenuHoldAtEscortPosition() -for _,MenuHoldAtEscortPosition in pairs(self.Menu.HoldAtEscortPosition or{})do -local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) -local FlightMenuHoldPosition=MENU_GROUP_COMMAND -:New( -self.PlayerGroup, -MenuHoldAtEscortPosition.MenuText, -FlightMenuReportNavigation, -AI_ESCORT._FlightHoldPosition, -self, -nil, -MenuHoldAtEscortPosition.Height, -MenuHoldAtEscortPosition.Speed -) -end -return self -end -function AI_ESCORT:SetEscortMenuHoldAtEscortPosition(EscortGroup) -for _,HoldAtEscortPosition in pairs(self.Menu.HoldAtEscortPosition or{})do -if EscortGroup:IsAir()then -local EscortGroupName=EscortGroup:GetName() -local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) -local EscortMenuHoldPosition=MENU_GROUP_COMMAND -:New( -self.PlayerGroup, -HoldAtEscortPosition.MenuText, -EscortMenuReportNavigation, -AI_ESCORT._HoldPosition, -self, -EscortGroup, -EscortGroup, -HoldAtEscortPosition.Height, -HoldAtEscortPosition.Speed -) -end -end -return self -end -function AI_ESCORT:MenuHoldAtEscortPosition(Height,Speed,MenuTextFormat) -self:F({Height,Speed,MenuTextFormat}) -if not Height then -Height=30 -end -if not Speed then -Speed=0 -end -local MenuText="" -if not MenuTextFormat then -if Speed==0 then -MenuText=string.format("Hold at %d meter",Height) -else -MenuText=string.format("Hold at %d meter at %d",Height,Speed) -end -else -if Speed==0 then -MenuText=string.format(MenuTextFormat,Height) -else -MenuText=string.format(MenuTextFormat,Height,Speed) -end -end -self.Menu.HoldAtEscortPosition=self.Menu.HoldAtEscortPosition or{} -self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition+1]={} -self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Height=Height -self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Speed=Speed -self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].MenuText=MenuText -return self -end -function AI_ESCORT:SetFlightMenuHoldAtLeaderPosition() -for _,MenuHoldAtLeaderPosition in pairs(self.Menu.HoldAtLeaderPosition or{})do -local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) -local FlightMenuHoldAtLeaderPosition=MENU_GROUP_COMMAND -:New( -self.PlayerGroup, -MenuHoldAtLeaderPosition.MenuText, -FlightMenuReportNavigation, -AI_ESCORT._FlightHoldPosition, -self, -self.PlayerGroup, -MenuHoldAtLeaderPosition.Height, -MenuHoldAtLeaderPosition.Speed -) -end -return self -end -function AI_ESCORT:SetEscortMenuHoldAtLeaderPosition(EscortGroup) -for _,HoldAtLeaderPosition in pairs(self.Menu.HoldAtLeaderPosition or{})do -if EscortGroup:IsAir()then -local EscortGroupName=EscortGroup:GetName() -local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) -local EscortMenuHoldAtLeaderPosition=MENU_GROUP_COMMAND -:New( -self.PlayerGroup, -HoldAtLeaderPosition.MenuText, -EscortMenuReportNavigation, -AI_ESCORT._HoldPosition, -self, -self.PlayerGroup, -EscortGroup, -HoldAtLeaderPosition.Height, -HoldAtLeaderPosition.Speed -) -end -end -return self -end -function AI_ESCORT:MenuHoldAtLeaderPosition(Height,Speed,MenuTextFormat) -self:F({Height,Speed,MenuTextFormat}) -if not Height then -Height=30 -end -if not Speed then -Speed=0 -end -local MenuText="" -if not MenuTextFormat then -if Speed==0 then -MenuText=string.format("Rejoin and hold at %d meter",Height) -else -MenuText=string.format("Rejoin and hold at %d meter at %d",Height,Speed) -end -else -if Speed==0 then -MenuText=string.format(MenuTextFormat,Height) -else -MenuText=string.format(MenuTextFormat,Height,Speed) -end -end -self.Menu.HoldAtLeaderPosition=self.Menu.HoldAtLeaderPosition or{} -self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition+1]={} -self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Height=Height -self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Speed=Speed -self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].MenuText=MenuText -return self -end -function AI_ESCORT:MenuScanForTargets(Height,Seconds,MenuTextFormat) -self:F({Height,Seconds,MenuTextFormat}) -if self.EscortGroup:IsAir()then -if not self.EscortMenuScan then -self.EscortMenuScan=MENU_GROUP:New(self.PlayerGroup,"Scan for targets",self.EscortMenu) -end -if not Height then -Height=100 -end -if not Seconds then -Seconds=30 -end -local MenuText="" -if not MenuTextFormat then -if Seconds==0 then -MenuText=string.format("At %d meter",Height) -else -MenuText=string.format("At %d meter for %d seconds",Height,Seconds) -end -else -if Seconds==0 then -MenuText=string.format(MenuTextFormat,Height) -else -MenuText=string.format(MenuTextFormat,Height,Seconds) -end -end -if not self.EscortMenuScanForTargets then -self.EscortMenuScanForTargets={} -end -self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1]=MENU_GROUP_COMMAND -:New( -self.PlayerGroup, -MenuText, -self.EscortMenuScan, -AI_ESCORT._ScanTargets, -self, -30 -) -end -return self -end -function AI_ESCORT:SetFlightMenuFlare() -for _,MenuFlare in pairs(self.Menu.Flare or{})do -local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) -local FlightMenuFlare=MENU_GROUP:New(self.PlayerGroup,MenuFlare.MenuText,FlightMenuReportNavigation) -local FlightMenuFlareGreenFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.Green,"Released a green flare!") -local FlightMenuFlareRedFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.Red,"Released a red flare!") -local FlightMenuFlareWhiteFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.White,"Released a white flare!") -local FlightMenuFlareYellowFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release yellow flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.Yellow,"Released a yellow flare!") -end -return self -end -function AI_ESCORT:SetEscortMenuFlare(EscortGroup) -for _,MenuFlare in pairs(self.Menu.Flare or{})do -if EscortGroup:IsAir()then -local EscortGroupName=EscortGroup:GetName() -local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) -local EscortMenuFlare=MENU_GROUP:New(self.PlayerGroup,MenuFlare.MenuText,EscortMenuReportNavigation) -local EscortMenuFlareGreen=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.Green,"Released a green flare!") -local EscortMenuFlareRed=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.Red,"Released a red flare!") -local EscortMenuFlareWhite=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.White,"Released a white flare!") -local EscortMenuFlareYellow=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release yellow flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.Yellow,"Released a yellow flare!") -end -end -return self -end -function AI_ESCORT:MenuFlare(MenuTextFormat) -self:F() -local MenuText="" -if not MenuTextFormat then -MenuText="Flare" -else -MenuText=MenuTextFormat -end -self.Menu.Flare=self.Menu.Flare or{} -self.Menu.Flare[#self.Menu.Flare+1]={} -self.Menu.Flare[#self.Menu.Flare].MenuText=MenuText -return self -end -function AI_ESCORT:SetFlightMenuSmoke() -for _,MenuSmoke in pairs(self.Menu.Smoke or{})do -local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) -local FlightMenuSmoke=MENU_GROUP:New(self.PlayerGroup,MenuSmoke.MenuText,FlightMenuReportNavigation) -local FlightMenuSmokeGreenFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Green,"Releasing green smoke!") -local FlightMenuSmokeRedFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Red,"Releasing red smoke!") -local FlightMenuSmokeWhiteFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.White,"Releasing white smoke!") -local FlightMenuSmokeOrangeFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release orange smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Orange,"Releasing orange smoke!") -local FlightMenuSmokeBlueFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release blue smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Blue,"Releasing blue smoke!") -end -return self -end -function AI_ESCORT:SetEscortMenuSmoke(EscortGroup) -for _,MenuSmoke in pairs(self.Menu.Smoke or{})do -if EscortGroup:IsAir()then -local EscortGroupName=EscortGroup:GetName() -local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) -local EscortMenuSmoke=MENU_GROUP:New(self.PlayerGroup,MenuSmoke.MenuText,EscortMenuReportNavigation) -local EscortMenuSmokeGreen=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Green,"Releasing green smoke!") -local EscortMenuSmokeRed=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Red,"Releasing red smoke!") -local EscortMenuSmokeWhite=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.White,"Releasing white smoke!") -local EscortMenuSmokeOrange=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release orange smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Orange,"Releasing orange smoke!") -local EscortMenuSmokeBlue=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release blue smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Blue,"Releasing blue smoke!") -end -end -return self -end -function AI_ESCORT:MenuSmoke(MenuTextFormat) -self:F() -local MenuText="" -if not MenuTextFormat then -MenuText="Smoke" -else -MenuText=MenuTextFormat -end -self.Menu.Smoke=self.Menu.Smoke or{} -self.Menu.Smoke[#self.Menu.Smoke+1]={} -self.Menu.Smoke[#self.Menu.Smoke].MenuText=MenuText -return self -end -function AI_ESCORT:SetFlightMenuReportType() -local FlightMenuReportTargets=MENU_GROUP:New(self.PlayerGroup,"Report targets",self.FlightMenu) -local MenuStamp=FlightMenuReportTargets:GetStamp() -local FlightReportType=self:GetFlightReportType() -if FlightReportType~=self.__Enum.ReportType.All then -local FlightMenuReportTargetsAll=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report all targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeAll,self) -:SetTag("ReportType") -:SetStamp(MenuStamp) -end -if FlightReportType==self.__Enum.ReportType.All or FlightReportType~=self.__Enum.ReportType.Airborne then -local FlightMenuReportTargetsAirborne=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report airborne targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeAirborne,self) -:SetTag("ReportType") -:SetStamp(MenuStamp) -end -if FlightReportType==self.__Enum.ReportType.All or FlightReportType~=self.__Enum.ReportType.GroundRadar then -local FlightMenuReportTargetsGroundRadar=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report gound radar targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeGroundRadar,self) -:SetTag("ReportType") -:SetStamp(MenuStamp) -end -if FlightReportType==self.__Enum.ReportType.All or FlightReportType~=self.__Enum.ReportType.Ground then -local FlightMenuReportTargetsGround=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report ground targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeGround,self) -:SetTag("ReportType") -:SetStamp(MenuStamp) -end -FlightMenuReportTargets:RemoveSubMenus(MenuStamp,"ReportType") -end -function AI_ESCORT:SetFlightMenuTargets() -local FlightMenuReportTargets=MENU_GROUP:New(self.PlayerGroup,"Report targets",self.FlightMenu) -local FlightMenuReportTargetsNow=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets now!",FlightMenuReportTargets,AI_ESCORT._FlightReportNearbyTargetsNow,self) -local FlightMenuReportTargetsOn=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets on",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportNearbyTargets,self,true) -local FlightMenuReportTargetsOff=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets off",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportNearbyTargets,self,false) -self.FlightMenuAttack=MENU_GROUP:New(self.PlayerGroup,"Attack targets",self.FlightMenu) -local FlightMenuAttackNearby=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Attack nearest targets",self.FlightMenuAttack,AI_ESCORT._FlightAttackNearestTarget,self):SetTag("Attack") -local FlightMenuAttackNearbyAir=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Attack nearest airborne targets",self.FlightMenuAttack,AI_ESCORT._FlightAttackNearestTarget,self,self.__Enum.ReportType.Air):SetTag("Attack") -local FlightMenuAttackNearbyGround=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Attack nearest ground targets",self.FlightMenuAttack,AI_ESCORT._FlightAttackNearestTarget,self,self.__Enum.ReportType.Ground):SetTag("Attack") -for _,MenuTargets in pairs(self.Menu.Targets or{})do -MenuTargets.FlightReportTargetsScheduler=SCHEDULER:New(self,self._FlightReportTargetsScheduler,{},MenuTargets.Interval,MenuTargets.Interval) -end -return self -end -function AI_ESCORT:SetEscortMenuTargets(EscortGroup) -for _,MenuTargets in pairs(self.Menu.Targets or{}or{})do -if EscortGroup:IsAir()then -local EscortGroupName=EscortGroup:GetName() -EscortGroup.EscortMenuReportNearbyTargetsNow=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets",EscortGroup.EscortMenu,AI_ESCORT._ReportNearbyTargetsNow,self,EscortGroup,true) -EscortGroup.ReportTargetsScheduler=SCHEDULER:New(self,self._ReportTargetsScheduler,{EscortGroup},1,MenuTargets.Interval) -EscortGroup.ResumeScheduler=SCHEDULER:New(self,self._ResumeScheduler,{EscortGroup},1,60) -end -end -return self -end -function AI_ESCORT:MenuTargets(Seconds) -self:F({Seconds}) -if not Seconds then -Seconds=30 -end -self.Menu.Targets=self.Menu.Targets or{} -self.Menu.Targets[#self.Menu.Targets+1]={} -self.Menu.Targets[#self.Menu.Targets].Interval=Seconds -return self -end -function AI_ESCORT:MenuAssistedAttack() -self:F() -self.EscortGroupSet:ForSomeGroupAlive( -function(EscortGroup) -if not EscortGroup:IsAir()then -self.EscortMenuTargetAssistance=MENU_GROUP:New(self.PlayerGroup,"Request assistance from",EscortGroup.EscortMenu) -end -end -) -return self -end -function AI_ESCORT:SetFlightMenuROE() -for _,MenuROE in pairs(self.Menu.ROE or{})do -local FlightMenuROE=MENU_GROUP:New(self.PlayerGroup,"Rule Of Engagement",self.FlightMenu) -local FlightMenuROEHoldFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Hold fire",FlightMenuROE,AI_ESCORT._FlightROEHoldFire,self,"Holding weapons!") -local FlightMenuROEReturnFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Return fire",FlightMenuROE,AI_ESCORT._FlightROEReturnFire,self,"Returning fire!") -local FlightMenuROEOpenFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open Fire",FlightMenuROE,AI_ESCORT._FlightROEOpenFire,self,"Open fire at designated targets!") -local FlightMenuROEWeaponFree=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Engage all targets",FlightMenuROE,AI_ESCORT._FlightROEWeaponFree,self,"Engaging all targets!") -end -return self -end -function AI_ESCORT:SetEscortMenuROE(EscortGroup) -for _,MenuROE in pairs(self.Menu.ROE or{})do -if EscortGroup:IsAir()then -local EscortGroupName=EscortGroup:GetName() -local EscortMenuROE=MENU_GROUP:New(self.PlayerGroup,"Rule Of Engagement",EscortGroup.EscortMenu) -if EscortGroup:OptionROEHoldFirePossible()then -local EscortMenuROEHoldFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Hold fire",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEHoldFire,"Holding weapons!") -end -if EscortGroup:OptionROEReturnFirePossible()then -local EscortMenuROEReturnFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Return fire",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEReturnFire,"Returning fire!") -end -if EscortGroup:OptionROEOpenFirePossible()then -EscortGroup.EscortMenuROEOpenFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open Fire",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEOpenFire,"Opening fire on designated targets!!") -end -if EscortGroup:OptionROEWeaponFreePossible()then -EscortGroup.EscortMenuROEWeaponFree=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Engage all targets",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEWeaponFree,"Opening fire on targets of opportunity!") -end -end -end -return self -end -function AI_ESCORT:MenuROE() -self:F() -self.Menu.ROE=self.Menu.ROE or{} -self.Menu.ROE[#self.Menu.ROE+1]={} -return self -end -function AI_ESCORT:SetFlightMenuROT() -for _,MenuROT in pairs(self.Menu.ROT or{})do -local FlightMenuROT=MENU_GROUP:New(self.PlayerGroup,"Reaction On Threat",self.FlightMenu) -local FlightMenuROTNoReaction=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Fight until death",FlightMenuROT,AI_ESCORT._FlightROTNoReaction,self,"Fighting until death!") -local FlightMenuROTPassiveDefense=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Use flares, chaff and jammers",FlightMenuROT,AI_ESCORT._FlightROTPassiveDefense,self,"Defending using jammers, chaff and flares!") -local FlightMenuROTEvadeFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open fire",FlightMenuROT,AI_ESCORT._FlightROTEvadeFire,self,"Evading on enemy fire!") -local FlightMenuROTVertical=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Avoid radar and evade fire",FlightMenuROT,AI_ESCORT._FlightROTVertical,self,"Evading on enemy fire with vertical manoeuvres!") -end -return self -end -function AI_ESCORT:SetEscortMenuROT(EscortGroup) -for _,MenuROT in pairs(self.Menu.ROT or{})do -if EscortGroup:IsAir()then -local EscortGroupName=EscortGroup:GetName() -local EscortMenuROT=MENU_GROUP:New(self.PlayerGroup,"Reaction On Threat",EscortGroup.EscortMenu) -if not EscortGroup.EscortMenuEvasion then -if EscortGroup:OptionROTNoReactionPossible()then -local EscortMenuEvasionNoReaction=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Fight until death",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTNoReaction,"Fighting until death!") -end -if EscortGroup:OptionROTPassiveDefensePossible()then -local EscortMenuEvasionPassiveDefense=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Use flares, chaff and jammers",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTPassiveDefense,"Defending using jammers, chaff and flares!") -end -if EscortGroup:OptionROTEvadeFirePossible()then -local EscortMenuEvasionEvadeFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open fire",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTEvadeFire,"Evading on enemy fire!") -end -if EscortGroup:OptionROTVerticalPossible()then -local EscortMenuOptionEvasionVertical=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Avoid radar and evade fire",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTVertical,"Evading on enemy fire with vertical manoeuvres!") -end -end -end -end -return self -end -function AI_ESCORT:MenuROT(MenuTextFormat) -self:F(MenuTextFormat) -self.Menu.ROT=self.Menu.ROT or{} -self.Menu.ROT[#self.Menu.ROT+1]={} -return self -end -function AI_ESCORT:SetEscortMenuResumeMission(EscortGroup) -self:F() -if EscortGroup:IsAir()then -local EscortGroupName=EscortGroup:GetName() -EscortGroup.EscortMenuResumeMission=MENU_GROUP:New(self.PlayerGroup,"Resume from",EscortGroup.EscortMenu) -end -return self -end -function AI_ESCORT:_HoldPosition(OrbitGroup,EscortGroup,OrbitHeight,OrbitSeconds) -local EscortUnit=self.PlayerUnit -local OrbitUnit=OrbitGroup:GetUnit(1) -self:SetFlightModeMission(EscortGroup) -local PointFrom={} -local GroupVec3=EscortGroup:GetUnit(1):GetVec3() -PointFrom={} -PointFrom.x=GroupVec3.x -PointFrom.y=GroupVec3.z -PointFrom.speed=250 -PointFrom.type=AI.Task.WaypointType.TURNING_POINT -PointFrom.alt=GroupVec3.y -PointFrom.alt_type=AI.Task.AltitudeType.BARO -local OrbitPoint=OrbitUnit:GetVec2() -local PointTo={} -PointTo.x=OrbitPoint.x -PointTo.y=OrbitPoint.y -PointTo.speed=250 -PointTo.type=AI.Task.WaypointType.TURNING_POINT -PointTo.alt=OrbitHeight -PointTo.alt_type=AI.Task.AltitudeType.BARO -PointTo.task=EscortGroup:TaskOrbitCircleAtVec2(OrbitPoint,OrbitHeight,0) -local Points={PointFrom,PointTo} -EscortGroup:OptionROEHoldFire() -EscortGroup:OptionROTPassiveDefense() -EscortGroup:SetTask(EscortGroup:TaskRoute(Points),1) -EscortGroup:MessageTypeToGroup("Orbiting at current location.",MESSAGE.Type.Information,EscortUnit:GetGroup()) -end -function AI_ESCORT:_FlightHoldPosition(OrbitGroup,OrbitHeight,OrbitSeconds) -local EscortUnit=self.PlayerUnit -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup,OrbitGroup) -if EscortGroup:IsAir()then -if OrbitGroup==nil then -OrbitGroup=EscortGroup -end -self:_HoldPosition(OrbitGroup,EscortGroup,OrbitHeight,OrbitSeconds) -end -end,OrbitGroup -) -end -function AI_ESCORT:_JoinUp(EscortGroup) -local EscortUnit=self.PlayerUnit -self:SetFlightModeFormation(EscortGroup) -EscortGroup:MessageTypeToGroup("Joining up!",MESSAGE.Type.Information,EscortUnit:GetGroup()) -end -function AI_ESCORT:_FlightJoinUp() -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -if EscortGroup:IsAir()then -self:_JoinUp(EscortGroup) -end -end -) -end -function AI_ESCORT:_EscortFormationTrail(EscortGroup,XStart,XSpace,YStart) -self:FormationTrail(XStart,XSpace,YStart) -end -function AI_ESCORT:_FlightFormationTrail(XStart,XSpace,YStart) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -if EscortGroup:IsAir()then -self:_EscortFormationTrail(EscortGroup,XStart,XSpace,YStart) -end -end -) -end -function AI_ESCORT:_EscortFormationStack(EscortGroup,XStart,XSpace,YStart,YSpace) -self:FormationStack(XStart,XSpace,YStart,YSpace) -end -function AI_ESCORT:_FlightFormationStack(XStart,XSpace,YStart,YSpace) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -if EscortGroup:IsAir()then -self:_EscortFormationStack(EscortGroup,XStart,XSpace,YStart,YSpace) -end -end -) -end -function AI_ESCORT:_Flare(EscortGroup,Color,Message) -local EscortUnit=self.PlayerUnit -EscortGroup:GetUnit(1):Flare(Color) -EscortGroup:MessageTypeToGroup(Message,MESSAGE.Type.Information,EscortUnit:GetGroup()) -end -function AI_ESCORT:_FlightFlare(Color,Message) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -if EscortGroup:IsAir()then -self:_Flare(EscortGroup,Color,Message) -end -end -) -end -function AI_ESCORT:_Smoke(EscortGroup,Color,Message) -local EscortUnit=self.PlayerUnit -EscortGroup:GetUnit(1):Smoke(Color) -EscortGroup:MessageTypeToGroup(Message,MESSAGE.Type.Information,EscortUnit:GetGroup()) -end -function AI_ESCORT:_FlightSmoke(Color,Message) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -if EscortGroup:IsAir()then -self:_Smoke(EscortGroup,Color,Message) -end -end -) -end -function AI_ESCORT:_ReportNearbyTargetsNow(EscortGroup) -local EscortUnit=self.PlayerUnit -self:_ReportTargetsScheduler(EscortGroup) -end -function AI_ESCORT:_FlightReportNearbyTargetsNow() -self:_FlightReportTargetsScheduler() -end -function AI_ESCORT:_FlightSwitchReportNearbyTargets(ReportTargets) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -if EscortGroup:IsAir()then -self:_EscortSwitchReportNearbyTargets(EscortGroup,ReportTargets) -end -end -) -end -function AI_ESCORT:SetFlightReportType(ReportType) -self.FlightReportType=ReportType -end -function AI_ESCORT:GetFlightReportType() -return self.FlightReportType -end -function AI_ESCORT:_FlightSwitchReportTypeAll() -self:SetFlightReportType(self.__Enum.ReportType.All) -self:SetFlightMenuReportType() -local EscortGroup=self.EscortGroupSet:GetFirst() -EscortGroup:MessageTypeToGroup("Reporting all targets.",MESSAGE.Type.Information,self.PlayerGroup) -end -function AI_ESCORT:_FlightSwitchReportTypeAirborne() -self:SetFlightReportType(self.__Enum.ReportType.Airborne) -self:SetFlightMenuReportType() -local EscortGroup=self.EscortGroupSet:GetFirst() -EscortGroup:MessageTypeToGroup("Reporting airborne targets.",MESSAGE.Type.Information,self.PlayerGroup) -end -function AI_ESCORT:_FlightSwitchReportTypeGroundRadar() -self:SetFlightReportType(self.__Enum.ReportType.Ground) -self:SetFlightMenuReportType() -local EscortGroup=self.EscortGroupSet:GetFirst() -EscortGroup:MessageTypeToGroup("Reporting ground radar targets.",MESSAGE.Type.Information,self.PlayerGroup) -end -function AI_ESCORT:_FlightSwitchReportTypeGround() -self:SetFlightReportType(self.__Enum.ReportType.Ground) -self:SetFlightMenuReportType() -local EscortGroup=self.EscortGroupSet:GetFirst() -EscortGroup:MessageTypeToGroup("Reporting ground targets.",MESSAGE.Type.Information,self.PlayerGroup) -end -function AI_ESCORT:_ScanTargets(ScanDuration) -local EscortGroup=self.EscortGroup -local EscortUnit=self.PlayerUnit -self.FollowScheduler:Stop(self.FollowSchedule) -if EscortGroup:IsHelicopter()then -EscortGroup:PushTask( -EscortGroup:TaskControlled( -EscortGroup:TaskOrbitCircle(200,20), -EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil) -),1) -elseif EscortGroup:IsAirPlane()then -EscortGroup:PushTask( -EscortGroup:TaskControlled( -EscortGroup:TaskOrbitCircle(1000,500), -EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil) -),1) -end -EscortGroup:MessageToClient("Scanning targets for "..ScanDuration.." seconds.",ScanDuration,EscortUnit) -if self.EscortMode==AI_ESCORT.MODE.FOLLOW then -self.FollowScheduler:Start(self.FollowSchedule) -end -end -function AI_ESCORT.___Resume(EscortGroup,self) -self:F({self=self}) -local PlayerGroup=self.PlayerGroup -EscortGroup:OptionROEHoldFire() -EscortGroup:OptionROTVertical() -EscortGroup:SetState(EscortGroup,"Mode",EscortGroup:GetState(EscortGroup,"PreviousMode")) -if EscortGroup:GetState(EscortGroup,"Mode")==self.__Enum.Mode.Mission then -EscortGroup:MessageTypeToGroup("Resuming route.",MESSAGE.Type.Information,PlayerGroup) -else -EscortGroup:MessageTypeToGroup("Rejoining formation.",MESSAGE.Type.Information,PlayerGroup) -end -end -function AI_ESCORT:_ResumeMission(EscortGroup,WayPoint) -self:SetFlightModeMission(EscortGroup) -local WayPoints=EscortGroup.MissionRoute -self:T(WayPoint,WayPoints) -for WayPointIgnore=1,WayPoint do -table.remove(WayPoints,1) -end -EscortGroup:SetTask(EscortGroup:TaskRoute(WayPoints),1) -EscortGroup:MessageTypeToGroup("Resuming mission from waypoint ",MESSAGE.Type.Information,self.PlayerGroup) -end -function AI_ESCORT:_AttackTarget(EscortGroup,DetectedItem) -self:F(EscortGroup) -self:SetFlightModeAttack(EscortGroup) -if EscortGroup:IsAir()then -EscortGroup:OptionROEOpenFire() -EscortGroup:OptionROTVertical() -EscortGroup:SetState(EscortGroup,"Escort",self) -local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) -local Tasks={} -local AttackUnitTasks={} -DetectedSet:ForEachUnit( -function(DetectedUnit,Tasks) -if DetectedUnit:IsAlive()then -AttackUnitTasks[#AttackUnitTasks+1]=EscortGroup:TaskAttackUnit(DetectedUnit) -end -end,Tasks -) -Tasks[#Tasks+1]=EscortGroup:TaskCombo(AttackUnitTasks) -Tasks[#Tasks+1]=EscortGroup:TaskFunction("AI_ESCORT.___Resume",self) -EscortGroup:PushTask( -EscortGroup:TaskCombo( -Tasks -),1 -) -else -local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) -local Tasks={} -DetectedSet:ForEachUnit( -function(DetectedUnit,Tasks) -if DetectedUnit:IsAlive()then -Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50) -end -end,Tasks -) -EscortGroup:PushTask( -EscortGroup:TaskCombo( -Tasks -),1 -) -end -local DetectedTargetsReport=REPORT:New("Engaging target:\n") -local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,self.PlayerGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())) -local ReportSummary=DetectedItemReportSummary:Text(", ") -DetectedTargetsReport:AddIndent(ReportSummary,"-") -EscortGroup:MessageTypeToGroup(DetectedTargetsReport:Text(),MESSAGE.Type.Information,self.PlayerGroup) -end -function AI_ESCORT:_FlightAttackTarget(DetectedItem) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup,DetectedItem) -if EscortGroup:IsAir()then -self:_AttackTarget(EscortGroup,DetectedItem) -end -end,DetectedItem -) -end -function AI_ESCORT:_FlightAttackNearestTarget(TargetType) -self.Detection:Detect() -self:_FlightReportTargetsScheduler() -local EscortGroup=self.EscortGroupSet:GetFirst() -local AttackDetectedItem=nil -local DetectedItems=self.Detection:GetDetectedItems() -for DetectedItemIndex,DetectedItem in UTILS.spairs(DetectedItems,function(t,a,b)return self:Distance(self.PlayerUnit,t[a])0 -local HasAir=DetectedItemSet:HasAirUnits()>0 -local FlightReportType=self:GetFlightReportType() -if(TargetType and TargetType==self.__Enum.ReportType.Ground and HasGround)or -(TargetType and TargetType==self.__Enum.ReportType.Air and HasAir)or -(TargetType==nil)then -AttackDetectedItem=DetectedItem -break -end -end -if AttackDetectedItem then -self:_FlightAttackTarget(AttackDetectedItem) -else -EscortGroup:MessageTypeToGroup("Nothing to attack!",MESSAGE.Type.Information,self.PlayerGroup) -end -end -function AI_ESCORT:_AssistTarget(EscortGroup,DetectedItem) -local EscortUnit=self.PlayerUnit -local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) -local Tasks={} -DetectedSet:ForEachUnit( -function(DetectedUnit,Tasks) -if DetectedUnit:IsAlive()then -Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50) -end -end,Tasks -) -EscortGroup:SetTask( -EscortGroup:TaskCombo( -Tasks -),1 -) -EscortGroup:MessageTypeToGroup("Assisting attack!",MESSAGE.Type.Information,EscortUnit:GetGroup()) -end -function AI_ESCORT:_ROE(EscortGroup,EscortROEFunction,EscortROEMessage) -pcall(function()EscortROEFunction(EscortGroup)end) -EscortGroup:MessageTypeToGroup(EscortROEMessage,MESSAGE.Type.Information,self.PlayerGroup) -end -function AI_ESCORT:_FlightROEHoldFire(EscortROEMessage) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -self:_ROE(EscortGroup,EscortGroup.OptionROEHoldFire,EscortROEMessage) -end -) -end -function AI_ESCORT:_FlightROEOpenFire(EscortROEMessage) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -self:_ROE(EscortGroup,EscortGroup.OptionROEOpenFire,EscortROEMessage) -end -) -end -function AI_ESCORT:_FlightROEReturnFire(EscortROEMessage) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -self:_ROE(EscortGroup,EscortGroup.OptionROEReturnFire,EscortROEMessage) -end -) -end -function AI_ESCORT:_FlightROEWeaponFree(EscortROEMessage) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -self:_ROE(EscortGroup,EscortGroup.OptionROEWeaponFree,EscortROEMessage) -end -) -end -function AI_ESCORT:_ROT(EscortGroup,EscortROTFunction,EscortROTMessage) -pcall(function()EscortROTFunction(EscortGroup)end) -EscortGroup:MessageTypeToGroup(EscortROTMessage,MESSAGE.Type.Information,self.PlayerGroup) -end -function AI_ESCORT:_FlightROTNoReaction(EscortROTMessage) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -self:_ROT(EscortGroup,EscortGroup.OptionROTNoReaction,EscortROTMessage) -end -) -end -function AI_ESCORT:_FlightROTPassiveDefense(EscortROTMessage) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -self:_ROT(EscortGroup,EscortGroup.OptionROTPassiveDefense,EscortROTMessage) -end -) -end -function AI_ESCORT:_FlightROTEvadeFire(EscortROTMessage) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -self:_ROT(EscortGroup,EscortGroup.OptionROTEvadeFire,EscortROTMessage) -end -) -end -function AI_ESCORT:_FlightROTVertical(EscortROTMessage) -self.EscortGroupSet:ForEachGroupAlive( -function(EscortGroup) -self:_ROT(EscortGroup,EscortGroup.OptionROTVertical,EscortROTMessage) -end -) -end -function AI_ESCORT:RegisterRoute() -self:F() -local EscortGroup=self.EscortGroup -local TaskPoints=EscortGroup:GetTaskRoute() -self:T(TaskPoints) -return TaskPoints -end -function AI_ESCORT:_ResumeScheduler(EscortGroup) -self:F(EscortGroup:GetName()) -if EscortGroup:IsAlive()and self.PlayerUnit:IsAlive()then -local EscortGroupName=EscortGroup:GetCallsign() -if EscortGroup.EscortMenuResumeMission then -EscortGroup.EscortMenuResumeMission:RemoveSubMenus() -local TaskPoints=EscortGroup.MissionRoute -for WayPointID,WayPoint in pairs(TaskPoints)do -local EscortVec3=EscortGroup:GetVec3() -local Distance=((WayPoint.x-EscortVec3.x)^2+ -(WayPoint.y-EscortVec3.z)^2 -)^0.5/1000 -MENU_GROUP_COMMAND:New(self.PlayerGroup,"Waypoint "..WayPointID.." at "..string.format("%.2f",Distance).."km",EscortGroup.EscortMenuResumeMission,AI_ESCORT._ResumeMission,self,EscortGroup,WayPointID) -end -end -end -end -function AI_ESCORT:Distance(PlayerUnit,DetectedItem) -local DetectedCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem) -local PlayerCoordinate=PlayerUnit:GetCoordinate() -return DetectedCoordinate:Get3DDistance(PlayerCoordinate) -end -function AI_ESCORT:_ReportTargetsScheduler(EscortGroup,Report) -self:F(EscortGroup:GetName()) -if EscortGroup:IsAlive()and self.PlayerUnit:IsAlive()then -local EscortGroupName=EscortGroup:GetCallsign() -local DetectedTargetsReport=REPORT:New("Reporting targets:\n") -if EscortGroup.EscortMenuTargetAssistance then -EscortGroup.EscortMenuTargetAssistance:RemoveSubMenus() -end -local DetectedItems=self.Detection:GetDetectedItems() -local ClientEscortTargets=self.Detection -local TimeUpdate=timer.getTime() -local EscortMenuAttackTargets=MENU_GROUP:New(self.PlayerGroup,"Attack targets",EscortGroup.EscortMenu) -local DetectedTargets=false -for DetectedItemIndex,DetectedItem in UTILS.spairs(DetectedItems,function(t,a,b)return self:Distance(self.PlayerUnit,t[a])0 -local HasGroundRadar=HasGround and DetectedItemSet:HasRadar()>0 -local HasAir=DetectedItemSet:HasAirUnits()>0 -local FlightReportType=self:GetFlightReportType() -if(FlightReportType==self.__Enum.ReportType.All)or -(FlightReportType==self.__Enum.ReportType.Airborne and HasAir)or -(FlightReportType==self.__Enum.ReportType.Ground and HasGround)or -(FlightReportType==self.__Enum.ReportType.GroundRadar and HasGroundRadar)then -DetectedTargets=true -local DetectedMenu=self.Detection:DetectedItemReportMenu(DetectedItem,EscortGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())):Text("\n") -local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,EscortGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())) -local ReportSummary=DetectedItemReportSummary:Text(", ") -DetectedTargetsReport:AddIndent(ReportSummary,"-") -if EscortGroup:IsAir()then -MENU_GROUP_COMMAND:New(self.PlayerGroup, -DetectedMenu, -EscortMenuAttackTargets, -AI_ESCORT._AttackTarget, -self, -EscortGroup, -DetectedItem -):SetTag("Escort"):SetTime(TimeUpdate) -else -if self.EscortMenuTargetAssistance then -local MenuTargetAssistance=MENU_GROUP:New(self.PlayerGroup,EscortGroupName,EscortGroup.EscortMenuTargetAssistance) -MENU_GROUP_COMMAND:New(self.PlayerGroup, -DetectedMenu, -MenuTargetAssistance, -AI_ESCORT._AssistTarget, -self, -EscortGroup, -DetectedItem -) -end -end -end -end -EscortMenuAttackTargets:RemoveSubMenus(TimeUpdate,"Escort") -if Report then -if DetectedTargets then -EscortGroup:MessageTypeToGroup(DetectedTargetsReport:Text("\n"),MESSAGE.Type.Information,self.PlayerGroup) -else -EscortGroup:MessageTypeToGroup("No targets detected.",MESSAGE.Type.Information,self.PlayerGroup) -end -end -return true -end -return false -end -function AI_ESCORT:_FlightReportTargetsScheduler() -self:F("FlightReportTargetScheduler") -local EscortGroup=self.EscortGroupSet:GetFirst() -local DetectedTargetsReport=REPORT:New("Reporting your targets:\n") -if EscortGroup and(self.PlayerUnit:IsAlive()and EscortGroup:IsAlive())then -local TimeUpdate=timer.getTime() -local DetectedItems=self.Detection:GetDetectedItems() -local DetectedTargets=false -local ClientEscortTargets=self.Detection -for DetectedItemIndex,DetectedItem in UTILS.spairs(DetectedItems,function(t,a,b)return self:Distance(self.PlayerUnit,t[a])0 -local HasGroundRadar=HasGround and DetectedItemSet:HasRadar()>0 -local HasAir=DetectedItemSet:HasAirUnits()>0 -local FlightReportType=self:GetFlightReportType() -if(FlightReportType==self.__Enum.ReportType.All)or -(FlightReportType==self.__Enum.ReportType.Airborne and HasAir)or -(FlightReportType==self.__Enum.ReportType.Ground and HasGround)or -(FlightReportType==self.__Enum.ReportType.GroundRadar and HasGroundRadar)then -DetectedTargets=true -local DetectedItemReportMenu=self.Detection:DetectedItemReportMenu(DetectedItem,self.PlayerGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())) -local ReportMenuText=DetectedItemReportMenu:Text(", ") -MENU_GROUP_COMMAND:New(self.PlayerGroup, -ReportMenuText, -self.FlightMenuAttack, -AI_ESCORT._FlightAttackTarget, -self, -DetectedItem -):SetTag("Flight"):SetTime(TimeUpdate) -local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,self.PlayerGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())) -local ReportSummary=DetectedItemReportSummary:Text(", ") -DetectedTargetsReport:AddIndent(ReportSummary,"-") -end -end -self.FlightMenuAttack:RemoveSubMenus(TimeUpdate,"Flight") -if DetectedTargets then -EscortGroup:MessageTypeToGroup(DetectedTargetsReport:Text("\n"),MESSAGE.Type.Information,self.PlayerGroup) -end -return true -end -return false -end -AI_ESCORT_REQUEST={ -ClassName="AI_ESCORT_REQUEST", -} -function AI_ESCORT_REQUEST:New(EscortUnit,EscortSpawn,EscortAirbase,EscortName,EscortBriefing) -local EscortGroupSet=SET_GROUP:New():FilterDeads():FilterCrashes() -local self=BASE:Inherit(self,AI_ESCORT:New(EscortUnit,EscortGroupSet,EscortName,EscortBriefing)) -self.EscortGroupSet=EscortGroupSet -self.EscortSpawn=EscortSpawn -self.EscortAirbase=EscortAirbase -self.LeaderGroup=self.PlayerUnit:GetGroup() -self.Detection=DETECTION_AREAS:New(self.EscortGroupSet,5000) -self.Detection:__Start(30) -self.SpawnMode=self.__Enum.Mode.Mission -return self -end -function AI_ESCORT_REQUEST:SpawnEscort() -local EscortGroup=self.EscortSpawn:SpawnAtAirbase(self.EscortAirbase,SPAWN.Takeoff.Hot) -self:ScheduleOnce(0.1, -function(EscortGroup) -EscortGroup:OptionROTVertical() -EscortGroup:OptionROEHoldFire() -self.EscortGroupSet:AddGroup(EscortGroup) -local LeaderEscort=self.EscortGroupSet:GetFirst() -local Report=REPORT:New() -Report:Add("Joining Up "..self.EscortGroupSet:GetUnitTypeNames():Text(", ").." from "..LeaderEscort:GetCoordinate():ToString(self.EscortUnit)) -LeaderEscort:MessageTypeToGroup(Report:Text(),MESSAGE.Type.Information,self.PlayerUnit) -self:SetFlightModeFormation(EscortGroup) -self:FormationTrail() -self:_InitFlightMenus() -self:_InitEscortMenus(EscortGroup) -self:_InitEscortRoute(EscortGroup) -function EscortGroup:OnEventDeadOrCrash(EventData) -self:F({"EventDead",EventData}) -self.EscortMenu:Remove() -end -EscortGroup:HandleEvent(EVENTS.Dead,EscortGroup.OnEventDeadOrCrash) -EscortGroup:HandleEvent(EVENTS.Crash,EscortGroup.OnEventDeadOrCrash) -end,EscortGroup -) -end -function AI_ESCORT_REQUEST:onafterStart(EscortGroupSet) -self:F() -if not self.MenuRequestEscort then -self.MainMenu=MENU_GROUP:New(self.PlayerGroup,self.EscortName) -self.MenuRequestEscort=MENU_GROUP_COMMAND:New(self.LeaderGroup,"Request new escort ",self.MainMenu, -function() -self:SpawnEscort() -end -) -end -self:GetParent(self).onafterStart(self,EscortGroupSet) -self:HandleEvent(EVENTS.Dead,self.OnEventDeadOrCrash) -self:HandleEvent(EVENTS.Crash,self.OnEventDeadOrCrash) -end -function AI_ESCORT_REQUEST:onafterStop(EscortGroupSet) -self:F() -EscortGroupSet:ForEachGroup( -function(EscortGroup) -EscortGroup:WayPointInitialize() -EscortGroup:OptionROTVertical() -EscortGroup:OptionROEOpenFire() -end -) -self.Detection:Stop() -self.MainMenu:Remove() -end -function AI_ESCORT_REQUEST:SetEscortSpawnMission() -self.SpawnMode=self.__Enum.Mode.Mission -end -AI_ESCORT_DISPATCHER={ -ClassName="AI_ESCORT_DISPATCHER", -} -AI_ESCORT_DISPATCHER.AI_Escorts={} -function AI_ESCORT_DISPATCHER:New(CarrierSet,EscortSpawn,EscortAirbase,EscortName,EscortBriefing) -local self=BASE:Inherit(self,FSM:New()) -self.CarrierSet=CarrierSet -self.EscortSpawn=EscortSpawn -self.EscortAirbase=EscortAirbase -self.EscortName=EscortName -self.EscortBriefing=EscortBriefing -self:SetStartState("Idle") -self:AddTransition("Monitoring","Monitor","Monitoring") -self:AddTransition("Idle","Start","Monitoring") -self:AddTransition("Monitoring","Stop","Idle") -function self.CarrierSet.OnAfterRemoved(CarrierSet,From,Event,To,CarrierName,Carrier) -self:F({Carrier=Carrier:GetName()}) -end -return self -end -function AI_ESCORT_DISPATCHER:onafterStart(From,Event,To) -self:HandleEvent(EVENTS.Birth) -self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventExit) -self:HandleEvent(EVENTS.Crash,self.OnEventExit) -self:HandleEvent(EVENTS.Dead,self.OnEventExit) -end -function AI_ESCORT_DISPATCHER:OnEventExit(EventData) -local PlayerGroupName=EventData.IniGroupName -local PlayerGroup=EventData.IniGroup -local PlayerUnit=EventData.IniUnit -self:I({EscortAirbase=self.EscortAirbase}) -self:I({PlayerGroupName=PlayerGroupName}) -self:I({PlayerGroup=PlayerGroup}) -self:I({FirstGroup=self.CarrierSet:GetFirst()}) -self:I({FindGroup=self.CarrierSet:FindGroup(PlayerGroupName)}) -if self.CarrierSet:FindGroup(PlayerGroupName)then -if self.AI_Escorts[PlayerGroupName]then -self.AI_Escorts[PlayerGroupName]:Stop() -self.AI_Escorts[PlayerGroupName]=nil -end -end -end -function AI_ESCORT_DISPATCHER:OnEventBirth(EventData) -local PlayerGroupName=EventData.IniGroupName -local PlayerGroup=EventData.IniGroup -local PlayerUnit=EventData.IniUnit -self:I({EscortAirbase=self.EscortAirbase}) -self:I({PlayerGroupName=PlayerGroupName}) -self:I({PlayerGroup=PlayerGroup}) -self:I({FirstGroup=self.CarrierSet:GetFirst()}) -self:I({FindGroup=self.CarrierSet:FindGroup(PlayerGroupName)}) -if self.CarrierSet:FindGroup(PlayerGroupName)then -if not self.AI_Escorts[PlayerGroupName]then -local LeaderUnit=PlayerUnit -local EscortGroup=self.EscortSpawn:SpawnAtAirbase(self.EscortAirbase,SPAWN.Takeoff.Hot) -self:I({EscortGroup=EscortGroup}) -self:ScheduleOnce(1, -function(EscortGroup) -local EscortSet=SET_GROUP:New() -EscortSet:AddGroup(EscortGroup) -self.AI_Escorts[PlayerGroupName]=AI_ESCORT:New(LeaderUnit,EscortSet,self.EscortName,self.EscortBriefing) -self.AI_Escorts[PlayerGroupName]:FormationTrail(0,100,0) -if EscortGroup:IsHelicopter()then -self.AI_Escorts[PlayerGroupName]:MenusHelicopters() -else -self.AI_Escorts[PlayerGroupName]:MenusAirplanes() -end -self.AI_Escorts[PlayerGroupName]:__Start(0.1) -end,EscortGroup -) -end -end -end -AI_ESCORT_DISPATCHER_REQUEST={ -ClassName="AI_ESCORT_DISPATCHER_REQUEST", -} -AI_ESCORT_DISPATCHER_REQUEST.AI_Escorts={} -function AI_ESCORT_DISPATCHER_REQUEST:New(CarrierSet,EscortSpawn,EscortAirbase,EscortName,EscortBriefing) -local self=BASE:Inherit(self,FSM:New()) -self.CarrierSet=CarrierSet -self.EscortSpawn=EscortSpawn -self.EscortAirbase=EscortAirbase -self.EscortName=EscortName -self.EscortBriefing=EscortBriefing -self:SetStartState("Idle") -self:AddTransition("Monitoring","Monitor","Monitoring") -self:AddTransition("Idle","Start","Monitoring") -self:AddTransition("Monitoring","Stop","Idle") -function self.CarrierSet.OnAfterRemoved(CarrierSet,From,Event,To,CarrierName,Carrier) -self:F({Carrier=Carrier:GetName()}) -end -return self -end -function AI_ESCORT_DISPATCHER_REQUEST:onafterStart(From,Event,To) -self:HandleEvent(EVENTS.Birth) -self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventExit) -self:HandleEvent(EVENTS.Crash,self.OnEventExit) -self:HandleEvent(EVENTS.Dead,self.OnEventExit) -end -function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit(EventData) -local PlayerGroupName=EventData.IniGroupName -local PlayerGroup=EventData.IniGroup -local PlayerUnit=EventData.IniUnit -if self.CarrierSet:FindGroup(PlayerGroupName)then -if self.AI_Escorts[PlayerGroupName]then -self.AI_Escorts[PlayerGroupName]:Stop() -self.AI_Escorts[PlayerGroupName]=nil -end -end -end -function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth(EventData) -local PlayerGroupName=EventData.IniGroupName -local PlayerGroup=EventData.IniGroup -local PlayerUnit=EventData.IniUnit -if self.CarrierSet:FindGroup(PlayerGroupName)then -if not self.AI_Escorts[PlayerGroupName]then -local LeaderUnit=PlayerUnit -self:ScheduleOnce(0.1, -function() -self.AI_Escorts[PlayerGroupName]=AI_ESCORT_REQUEST:New(LeaderUnit,self.EscortSpawn,self.EscortAirbase,self.EscortName,self.EscortBriefing) -self.AI_Escorts[PlayerGroupName]:FormationTrail(0,100,0) -if PlayerGroup:IsHelicopter()then -self.AI_Escorts[PlayerGroupName]:MenusHelicopters() -else -self.AI_Escorts[PlayerGroupName]:MenusAirplanes() -end -self.AI_Escorts[PlayerGroupName]:__Start(0.1) -end -) -end -end -end -AI_CARGO={ -ClassName="AI_CARGO", -Coordinate=nil, -Carrier_Cargo={}, -} -function AI_CARGO:New(Carrier,CargoSet) -local self=BASE:Inherit(self,FSM_CONTROLLABLE:New(Carrier)) -self.CargoSet=CargoSet -self.CargoCarrier=Carrier -self:SetStartState("Unloaded") -self:AddTransition("Unloaded","Pickup","Unloaded") -self:AddTransition("*","Load","*") -self:AddTransition("*","Reload","*") -self:AddTransition("*","Board","*") -self:AddTransition("*","Loaded","Loaded") -self:AddTransition("Loaded","PickedUp","Loaded") -self:AddTransition("Loaded","Deploy","*") -self:AddTransition("*","Unload","*") -self:AddTransition("*","Unboard","*") -self:AddTransition("*","Unloaded","Unloaded") -self:AddTransition("Unloaded","Deployed","Unloaded") -for _,CarrierUnit in pairs(Carrier:GetUnits())do -local CarrierUnit=CarrierUnit -CarrierUnit:SetCargoBayWeightLimit() -end -self.Transporting=false -self.Relocating=false -return self -end -function AI_CARGO:IsTransporting() -return self.Transporting==true -end -function AI_CARGO:IsRelocating() -return self.Relocating==true -end -function AI_CARGO:onafterPickup(APC,From,Event,To,Coordinate,Speed,Height,PickupZone) -self.Transporting=false -self.Relocating=true -end -function AI_CARGO:onafterDeploy(APC,From,Event,To,Coordinate,Speed,Height,DeployZone) -self.Relocating=false -self.Transporting=true -end -function AI_CARGO:onbeforeLoad(Carrier,From,Event,To,PickupZone) -self:F({Carrier,From,Event,To}) -local Boarding=false -local LoadInterval=2 -local LoadDelay=1 -local Carrier_List={} -local Carrier_Weight={} -if Carrier and Carrier:IsAlive()then -self.Carrier_Cargo={} -for _,CarrierUnit in pairs(Carrier:GetUnits())do -local CarrierUnit=CarrierUnit -local CargoBayFreeWeight=CarrierUnit:GetCargoBayFreeWeight() -self:F({CargoBayFreeWeight=CargoBayFreeWeight}) -Carrier_List[#Carrier_List+1]=CarrierUnit -Carrier_Weight[CarrierUnit]=CargoBayFreeWeight -end -local Carrier_Count=#Carrier_List -local Carrier_Index=1 -local Loaded=false -for _,Cargo in UTILS.spairs(self.CargoSet:GetSet(),function(t,a,b)return t[a]:GetWeight()>t[b]:GetWeight()end)do -local Cargo=Cargo -self:F({IsUnLoaded=Cargo:IsUnLoaded(),IsDeployed=Cargo:IsDeployed(),Cargo:GetName(),Carrier:GetName()}) -for Carrier_Loop=1,#Carrier_List do -local CarrierUnit=Carrier_List[Carrier_Index] -Carrier_Index=Carrier_Index+1 -if Carrier_Index>Carrier_Count then -Carrier_Index=1 -end -if Cargo:IsUnLoaded()and not Cargo:IsDeployed()then -if Cargo:IsInLoadRadius(CarrierUnit:GetCoordinate())then -self:F({"In radius",CarrierUnit:GetName()}) -local CargoWeight=Cargo:GetWeight() -local CarrierSpace=Carrier_Weight[CarrierUnit] -if CarrierSpace>CargoWeight then -Carrier:RouteStop() -Cargo:__Board(-LoadDelay,CarrierUnit) -self:__Board(LoadDelay,Cargo,CarrierUnit,PickupZone) -LoadDelay=LoadDelay+Cargo:GetCount()*LoadInterval -self.Carrier_Cargo[Cargo]=CarrierUnit -Boarding=true -Carrier_Weight[CarrierUnit]=Carrier_Weight[CarrierUnit]-CargoWeight -Loaded=true -break -else -self:T(string.format("WARNING: Cargo too heavy for carrier %s. Cargo=%.1f > %.1f free space",tostring(CarrierUnit:GetName()),CargoWeight,CarrierSpace)) -end -end -end -end -end -if not Loaded==true then -self.Relocating=false -end -end -return Boarding -end -function AI_CARGO:onbeforeReload(Carrier,From,Event,To) -self:F({Carrier,From,Event,To}) -local Boarding=false -local LoadInterval=2 -local LoadDelay=1 -local Carrier_List={} -local Carrier_Weight={} -if Carrier and Carrier:IsAlive()then -for _,CarrierUnit in pairs(Carrier:GetUnits())do -local CarrierUnit=CarrierUnit -Carrier_List[#Carrier_List+1]=CarrierUnit -end -local Carrier_Count=#Carrier_List -local Carrier_Index=1 -local Loaded=false -for Cargo,CarrierUnit in pairs(self.Carrier_Cargo)do -local Cargo=Cargo -self:F({IsUnLoaded=Cargo:IsUnLoaded(),IsDeployed=Cargo:IsDeployed(),Cargo:GetName(),Carrier:GetName()}) -for Carrier_Loop=1,#Carrier_List do -local CarrierUnit=Carrier_List[Carrier_Index] -Carrier_Index=Carrier_Index+1 -if Carrier_Index>Carrier_Count then -Carrier_Index=1 -end -if Cargo:IsUnLoaded()and not Cargo:IsDeployed()then -Carrier:RouteStop() -Cargo:__Board(-LoadDelay,CarrierUnit) -self:__Board(LoadDelay,Cargo,CarrierUnit) -LoadDelay=LoadDelay+Cargo:GetCount()*LoadInterval -self.Carrier_Cargo[Cargo]=CarrierUnit -Boarding=true -Loaded=true -end -end -end -if not Loaded==true then -self.Relocating=false -end -end -return Boarding -end -function AI_CARGO:onafterBoard(Carrier,From,Event,To,Cargo,CarrierUnit,PickupZone) -self:F({Carrier,From,Event,To,Cargo,CarrierUnit:GetName()}) -if Carrier and Carrier:IsAlive()then -self:F({IsLoaded=Cargo:IsLoaded(),Cargo:GetName(),Carrier:GetName()}) -if not Cargo:IsLoaded()and not Cargo:IsDestroyed()then -self:__Board(-10,Cargo,CarrierUnit,PickupZone) -return -end -end -self:__Loaded(0.1,Cargo,CarrierUnit,PickupZone) -end -function AI_CARGO:onafterLoaded(Carrier,From,Event,To,Cargo,PickupZone) -self:F({Carrier,From,Event,To}) -local Loaded=true -if Carrier and Carrier:IsAlive()then -for Cargo,CarrierUnit in pairs(self.Carrier_Cargo)do -local Cargo=Cargo -self:F({IsLoaded=Cargo:IsLoaded(),IsDestroyed=Cargo:IsDestroyed(),Cargo:GetName(),Carrier:GetName()}) -if not Cargo:IsLoaded()and not Cargo:IsDestroyed()then -Loaded=false -end -end -end -if Loaded then -self:__PickedUp(0.1,PickupZone) -end -end -function AI_CARGO:onafterPickedUp(Carrier,From,Event,To,PickupZone) -self:F({Carrier,From,Event,To}) -Carrier:RouteResume() -local HasCargo=false -if Carrier and Carrier:IsAlive()then -for Cargo,CarrierUnit in pairs(self.Carrier_Cargo)do -HasCargo=true -break -end -end -self.Relocating=false -if HasCargo then -self:F("Transporting") -self.Transporting=true -end -end -function AI_CARGO:onafterUnload(Carrier,From,Event,To,DeployZone,Defend) -self:F({Carrier,From,Event,To,DeployZone,Defend=Defend}) -local UnboardInterval=5 -local UnboardDelay=5 -if Carrier and Carrier:IsAlive()then -for _,CarrierUnit in pairs(Carrier:GetUnits())do -local CarrierUnit=CarrierUnit -Carrier:RouteStop() -for _,Cargo in pairs(CarrierUnit:GetCargo())do -self:F({Cargo=Cargo:GetName(),Isloaded=Cargo:IsLoaded()}) -if Cargo:IsLoaded()then -Cargo:__UnBoard(UnboardDelay) -UnboardDelay=UnboardDelay+Cargo:GetCount()*UnboardInterval -self:__Unboard(UnboardDelay,Cargo,CarrierUnit,DeployZone,Defend) -if not Defend==true then -Cargo:SetDeployed(true) -end -end -end -end -end -end -function AI_CARGO:onafterUnboard(Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend) -self:F({Carrier,From,Event,To,Cargo:GetName(),DeployZone=DeployZone,Defend=Defend}) -if Carrier and Carrier:IsAlive()then -if not Cargo:IsUnLoaded()then -self:__Unboard(10,Cargo,CarrierUnit,DeployZone,Defend) -return -end -end -self:Unloaded(Cargo,CarrierUnit,DeployZone,Defend) -end -function AI_CARGO:onafterUnloaded(Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend) -self:F({Carrier,From,Event,To,Cargo:GetName(),DeployZone=DeployZone,Defend=Defend}) -local AllUnloaded=true -if Carrier and Carrier:IsAlive()then -for _,CarrierUnit in pairs(Carrier:GetUnits())do -local CarrierUnit=CarrierUnit -local IsEmpty=CarrierUnit:IsCargoEmpty() -self:I({IsEmpty=IsEmpty}) -if not IsEmpty then -AllUnloaded=false -break -end -end -if AllUnloaded==true then -if DeployZone==true then -self.Carrier_Cargo={} -end -self.CargoCarrier=Carrier -end -end -if AllUnloaded==true then -self:__Deployed(5,DeployZone,Defend) -end -end -function AI_CARGO:onafterDeployed(Carrier,From,Event,To,DeployZone,Defend) -self:F({Carrier,From,Event,To,DeployZone=DeployZone,Defend=Defend}) -if not Defend==true then -self.Transporting=false -else -self:F("Defending") -end -end -AI_CARGO_APC={ -ClassName="AI_CARGO_APC", -Coordinate=nil, -} -function AI_CARGO_APC:New(APC,CargoSet,CombatRadius) -local self=BASE:Inherit(self,AI_CARGO:New(APC,CargoSet)) -self:AddTransition("*","Monitor","*") -self:AddTransition("*","Follow","Following") -self:AddTransition("*","Guard","Unloaded") -self:AddTransition("*","Home","*") -self:AddTransition("*","Reload","Boarding") -self:AddTransition("*","Deployed","*") -self:AddTransition("*","PickedUp","*") -self:AddTransition("*","Destroyed","Destroyed") -self:SetCombatRadius(CombatRadius) -self:SetCarrier(APC) -return self -end -function AI_CARGO_APC:SetCarrier(CargoCarrier) -self.CargoCarrier=CargoCarrier -self.CargoCarrier:SetState(self.CargoCarrier,"AI_CARGO_APC",self) -CargoCarrier:HandleEvent(EVENTS.Dead) -function CargoCarrier:OnEventDead(EventData) -self:F({"dead"}) -local AICargoTroops=self:GetState(self,"AI_CARGO_APC") -self:F({AICargoTroops=AICargoTroops}) -if AICargoTroops then -self:F({}) -if not AICargoTroops:Is("Loaded")then -AICargoTroops:Destroyed() -end -end -end -self.Zone=ZONE_UNIT:New(self.CargoCarrier:GetName().."-Zone",self.CargoCarrier,self.CombatRadius) -self.Coalition=self.CargoCarrier:GetCoalition() -self:SetControllable(CargoCarrier) -self:Guard() -return self -end -function AI_CARGO_APC:SetOffRoad(Offroad,Formation) -self:SetPickupOffRoad(Offroad,Formation) -self:SetDeployOffRoad(Offroad,Formation) -return self -end -function AI_CARGO_APC:SetPickupOffRoad(Offroad,Formation) -self.pickupOffroad=Offroad -self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad -return self -end -function AI_CARGO_APC:SetDeployOffRoad(Offroad,Formation) -self.deployOffroad=Offroad -self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad -return self -end -function AI_CARGO_APC:FindCarrier(Coordinate,Radius) -local CoordinateZone=ZONE_RADIUS:New("Zone",Coordinate:GetVec2(),Radius) -CoordinateZone:Scan({Object.Category.UNIT}) -for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do -local NearUnit=UNIT:Find(DCSUnit) -self:F({NearUnit=NearUnit}) -if not NearUnit:GetState(NearUnit,"AI_CARGO_APC")then -local Attributes=NearUnit:GetDesc() -self:F({Desc=Attributes}) -if NearUnit:HasAttribute("Trucks")then -return NearUnit:GetGroup() -end -end -end -return nil -end -function AI_CARGO_APC:SetCombatRadius(CombatRadius) -self.CombatRadius=CombatRadius or 0 -if self.CombatRadius>0 then -self:__Monitor(-5) -end -return self -end -function AI_CARGO_APC:FollowToCarrier(Me,APCUnit,CargoGroup) -local InfantryGroup=CargoGroup:GetGroup() -self:F({self=self:GetClassNameAndID(),InfantryGroup=InfantryGroup:GetName()}) -if APCUnit:IsAlive()then -if InfantryGroup:IsPartlyInZone(ZONE_UNIT:New("Radius",APCUnit,25))then -Me:Guard() -else -self:F({InfantryGroup=InfantryGroup:GetName()}) -if InfantryGroup:IsAlive()then -self:F({InfantryGroup=InfantryGroup:GetName()}) -local Waypoints={} -local FromCoord=InfantryGroup:GetCoordinate() -local FromGround=FromCoord:WaypointGround(10,"Diamond") -self:F({FromGround=FromGround}) -table.insert(Waypoints,FromGround) -local ToCoord=APCUnit:GetCoordinate():GetRandomCoordinateInRadius(10,5) -local ToGround=ToCoord:WaypointGround(10,"Diamond") -self:F({ToGround=ToGround}) -table.insert(Waypoints,ToGround) -local TaskRoute=InfantryGroup:TaskFunction("AI_CARGO_APC.FollowToCarrier",Me,APCUnit,CargoGroup) -self:F({Waypoints=Waypoints}) -local Waypoint=Waypoints[#Waypoints] -InfantryGroup:SetTaskWaypoint(Waypoint,TaskRoute) -InfantryGroup:Route(Waypoints,1) -end -end -end -end -function AI_CARGO_APC:onafterMonitor(APC,From,Event,To) -self:F({APC,From,Event,To,IsTransporting=self:IsTransporting()}) -if self.CombatRadius>0 then -if APC and APC:IsAlive()then -if self.CarrierCoordinate then -if self:IsTransporting()==true then -local Coordinate=APC:GetCoordinate() -if self:Is("Unloaded")or self:Is("Loaded")then -self.Zone:Scan({Object.Category.UNIT}) -if self.Zone:IsAllInZoneOfCoalition(self.Coalition)then -if self:Is("Unloaded")then -self:Reload() -end -else -if self:Is("Loaded")then -self:__Unload(1,nil,true) -else -if self:Is("Unloaded")then -end -self:F("I am here"..self:GetCurrentState()) -if self:Is("Following")then -for Cargo,APCUnit in pairs(self.Carrier_Cargo)do -local Cargo=Cargo -local APCUnit=APCUnit -if Cargo:IsAlive()then -if not Cargo:IsNear(APCUnit,40)then -APCUnit:RouteStop() -self.CarrierStopped=true -else -if self.CarrierStopped then -if Cargo:IsNear(APCUnit,25)then -APCUnit:RouteResume() -self.CarrierStopped=nil -end -end -end -end -end -end -end -end -end -end -end -self.CarrierCoordinate=APC:GetCoordinate() -end -self:__Monitor(-5) -end -end -function AI_CARGO_APC:onafterFollow(APC,From,Event,To) -self:F({APC,From,Event,To}) -self:F("Follow") -if APC and APC:IsAlive()then -for Cargo,APCUnit in pairs(self.Carrier_Cargo)do -local Cargo=Cargo -if Cargo:IsUnLoaded()then -self:FollowToCarrier(self,APCUnit,Cargo) -APCUnit:RouteResume() -end -end -end -end -function AI_CARGO_APC._Pickup(APC,self,Coordinate,Speed,PickupZone) -APC:F({"AI_CARGO_APC._Pickup:",APC:GetName()}) -if APC:IsAlive()then -self:Load(PickupZone) -end -end -function AI_CARGO_APC._Deploy(APC,self,Coordinate,DeployZone) -APC:F({"AI_CARGO_APC._Deploy:",APC}) -if APC:IsAlive()then -self:Unload(DeployZone) -end -end -function AI_CARGO_APC:onafterPickup(APC,From,Event,To,Coordinate,Speed,Height,PickupZone) -if APC and APC:IsAlive()then -if Coordinate then -self.RoutePickup=true -local _speed=Speed or APC:GetSpeedMax()*0.5 -local Waypoints={} -if self.pickupOffroad then -Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed,self.pickupFormation) -Waypoints[2]=Coordinate:WaypointGround(_speed,self.pickupFormation,DCSTasks) -else -Waypoints=APC:TaskGroundOnRoad(Coordinate,_speed,ENUMS.Formation.Vehicle.OffRoad,true) -end -local TaskFunction=APC:TaskFunction("AI_CARGO_APC._Pickup",self,Coordinate,Speed,PickupZone) -local Waypoint=Waypoints[#Waypoints] -APC:SetTaskWaypoint(Waypoint,TaskFunction) -APC:Route(Waypoints,1) -else -AI_CARGO_APC._Pickup(APC,self,Coordinate,Speed,PickupZone) -end -self:GetParent(self,AI_CARGO_APC).onafterPickup(self,APC,From,Event,To,Coordinate,Speed,Height,PickupZone) -end -end -function AI_CARGO_APC:onafterDeploy(APC,From,Event,To,Coordinate,Speed,Height,DeployZone) -if APC and APC:IsAlive()then -self.RouteDeploy=true -local speedmax=APC:GetSpeedMax() -local _speed=Speed or speedmax*0.5 -_speed=math.min(_speed,speedmax) -local Waypoints={} -if self.deployOffroad then -Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed,self.deployFormation) -Waypoints[2]=Coordinate:WaypointGround(_speed,self.deployFormation,DCSTasks) -else -Waypoints=APC:TaskGroundOnRoad(Coordinate,_speed,ENUMS.Formation.Vehicle.OffRoad,true) -end -local TaskFunction=APC:TaskFunction("AI_CARGO_APC._Deploy",self,Coordinate,DeployZone) -local Waypoint=Waypoints[#Waypoints] -APC:SetTaskWaypoint(Waypoint,TaskFunction) -APC:Route(Waypoints,1) -self:GetParent(self,AI_CARGO_APC).onafterDeploy(self,APC,From,Event,To,Coordinate,Speed,Height,DeployZone) -end -end -function AI_CARGO_APC:onafterUnloaded(Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend) -self:F({Carrier,From,Event,To,DeployZone=DeployZone,Defend=Defend}) -self:GetParent(self,AI_CARGO_APC).onafterUnloaded(self,Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend) -if Defend==true then -self.Zone:Scan({Object.Category.UNIT}) -if not self.Zone:IsAllInZoneOfCoalition(self.Coalition)then -local AttackUnits=self.Zone:GetScannedUnits() -local Move={} -local CargoGroup=Cargo.CargoObject -Move[#Move+1]=CargoGroup:GetCoordinate():WaypointGround(70,"Custom") -for UnitId,AttackUnit in pairs(AttackUnits)do -local MooseUnit=UNIT:Find(AttackUnit) -if MooseUnit:GetCoalition()~=CargoGroup:GetCoalition()then -Move[#Move+1]=MooseUnit:GetCoordinate():WaypointGround(70,"Line abreast") -self:F({MooseUnit=MooseUnit:GetName(),CargoGroup=CargoGroup:GetName()}) -end -end -CargoGroup:RoutePush(Move,0.1) -end -end -end -function AI_CARGO_APC:onafterDeployed(APC,From,Event,To,DeployZone,Defend) -self:F({APC,From,Event,To,DeployZone=DeployZone,Defend=Defend}) -self:__Guard(0.1) -self:GetParent(self,AI_CARGO_APC).onafterDeployed(self,APC,From,Event,To,DeployZone,Defend) -end -function AI_CARGO_APC:onafterHome(APC,From,Event,To,Coordinate,Speed,Height,HomeZone) -if APC and APC:IsAlive()~=nil then -self.RouteHome=true -Speed=Speed or APC:GetSpeedMax()*0.5 -local Waypoints=APC:TaskGroundOnRoad(Coordinate,Speed,"Line abreast",true) -self:F({Waypoints=Waypoints}) -local Waypoint=Waypoints[#Waypoints] -APC:Route(Waypoints,1) -end -end -AI_CARGO_HELICOPTER={ -ClassName="AI_CARGO_HELICOPTER", -Coordinate=nil, -} -AI_CARGO_QUEUE={} -function AI_CARGO_HELICOPTER:New(Helicopter,CargoSet) -local self=BASE:Inherit(self,AI_CARGO:New(Helicopter,CargoSet)) -self.Zone=ZONE_GROUP:New(Helicopter:GetName(),Helicopter,300) -self:SetStartState("Unloaded") -self:AddTransition("Unloaded","Pickup","Unloaded") -self:AddTransition("*","Landed","*") -self:AddTransition("*","Load","*") -self:AddTransition("*","Loaded","Loaded") -self:AddTransition("Loaded","PickedUp","Loaded") -self:AddTransition("Loaded","Deploy","*") -self:AddTransition("*","Queue","*") -self:AddTransition("*","Orbit","*") -self:AddTransition("*","Destroyed","*") -self:AddTransition("*","Unload","*") -self:AddTransition("*","Unloaded","Unloaded") -self:AddTransition("Unloaded","Deployed","Unloaded") -self:AddTransition("*","Home","*") -Helicopter:HandleEvent(EVENTS.Crash, -function(Helicopter,EventData) -AI_CARGO_QUEUE[Helicopter]=nil -end -) -Helicopter:HandleEvent(EVENTS.Land, -function(Helicopter,EventData) -self:ScheduleOnce(60, -function(Helicopter) -AI_CARGO_QUEUE[Helicopter]=nil -end,Helicopter -) -end -) -self:SetCarrier(Helicopter) -self.landingspeed=15 -self.landingheight=5.5 -return self -end -function AI_CARGO_HELICOPTER:SetCarrier(Helicopter) -local AICargo=self -self.Helicopter=Helicopter -self.Helicopter:SetState(self.Helicopter,"AI_CARGO_HELICOPTER",self) -self.RoutePickup=false -self.RouteDeploy=false -Helicopter:HandleEvent(EVENTS.Dead) -Helicopter:HandleEvent(EVENTS.Hit) -Helicopter:HandleEvent(EVENTS.Land) -function Helicopter:OnEventDead(EventData) -local AICargoTroops=self:GetState(self,"AI_CARGO_HELICOPTER") -self:F({AICargoTroops=AICargoTroops}) -if AICargoTroops then -self:F({}) -if not AICargoTroops:Is("Loaded")then -AICargoTroops:Destroyed() -end -end -end -function Helicopter:OnEventLand(EventData) -AICargo:Landed() -end -self.Coalition=self.Helicopter:GetCoalition() -self:SetControllable(Helicopter) -return self -end -function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed,height) -local _speed=speed or 15 -local _height=height or 5.5 -self.landingheight=_height -self.landingspeed=_speed -return self -end -function AI_CARGO_HELICOPTER:onafterLanded(Helicopter,From,Event,To) -self:F({From,Event,To}) -Helicopter:F({Name=Helicopter:GetName()}) -if Helicopter and Helicopter:IsAlive()then -self:T({Helicopter:GetName(),Height=Helicopter:GetHeight(true),Velocity=Helicopter:GetVelocityKMH()}) -if self.RoutePickup==true then -if Helicopter:GetHeight(true)<=self.landingheight then -self:Load(self.PickupZone) -self.RoutePickup=false -end -end -if self.RouteDeploy==true then -if Helicopter:GetHeight(true)<=self.landingheight then -self:Unload(self.DeployZone) -self.RouteDeploy=false -end -end -end -end -function AI_CARGO_HELICOPTER:onafterQueue(Helicopter,From,Event,To,Coordinate,Speed,DeployZone) -self:F({From,Event,To,Coordinate,Speed,DeployZone}) -local HelicopterInZone=false -if Helicopter and Helicopter:IsAlive()==true then -local Distance=Coordinate:DistanceFromPointVec2(Helicopter:GetCoordinate()) -if Distance>2000 then -self:__Queue(-10,Coordinate,Speed,DeployZone) -else -local ZoneFree=true -for Helicopter,ZoneQueue in pairs(AI_CARGO_QUEUE)do -local ZoneQueue=ZoneQueue -if ZoneQueue:IsCoordinateInZone(Coordinate)then -ZoneFree=false -end -end -self:F({ZoneFree=ZoneFree}) -if ZoneFree==true then -local ZoneQueue=ZONE_RADIUS:New(Helicopter:GetName(),Coordinate:GetVec2(),100) -AI_CARGO_QUEUE[Helicopter]=ZoneQueue -local Route={} -local CoordinateTo=Coordinate -local landheight=CoordinateTo:GetLandHeight() -CoordinateTo.y=landheight+50 -local WaypointTo=CoordinateTo:WaypointAir( -"RADIO", -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, -50, -true -) -Route[#Route+1]=WaypointTo -local Tasks={} -Tasks[#Tasks+1]=Helicopter:TaskLandAtVec2(CoordinateTo:GetVec2()) -Route[#Route].task=Helicopter:TaskCombo(Tasks) -Route[#Route+1]=WaypointTo -Helicopter:Route(Route,0) -self.DeployZone=DeployZone -else -self:__Queue(-10,Coordinate,Speed,DeployZone) -end -end -else -AI_CARGO_QUEUE[Helicopter]=nil -end -end -function AI_CARGO_HELICOPTER:onafterOrbit(Helicopter,From,Event,To,Coordinate) -self:F({From,Event,To,Coordinate}) -if Helicopter and Helicopter:IsAlive()then -local Route={} -local CoordinateTo=Coordinate -local landheight=CoordinateTo:GetLandHeight() -CoordinateTo.y=landheight+50 -local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,50,true) -Route[#Route+1]=WaypointTo -local Tasks={} -Tasks[#Tasks+1]=Helicopter:TaskOrbitCircle(math.random(30,80),150,CoordinateTo:GetRandomCoordinateInRadius(800,500)) -Route[#Route].task=Helicopter:TaskCombo(Tasks) -Route[#Route+1]=WaypointTo -Helicopter:Route(Route,0) -end -end -function AI_CARGO_HELICOPTER:onafterDeployed(Helicopter,From,Event,To,DeployZone) -self:F({From,Event,To,DeployZone=DeployZone}) -self:Orbit(Helicopter:GetCoordinate(),50) -self:ScheduleOnce(30, -function(Helicopter) -AI_CARGO_QUEUE[Helicopter]=nil -end,Helicopter -) -self:GetParent(self,AI_CARGO_HELICOPTER).onafterDeployed(self,Helicopter,From,Event,To,DeployZone) -end -function AI_CARGO_HELICOPTER:onafterPickup(Helicopter,From,Event,To,Coordinate,Speed,Height,PickupZone) -self:F({Coordinate,Speed,Height,PickupZone}) -if Helicopter and Helicopter:IsAlive()~=nil then -Helicopter:Activate() -self.RoutePickup=true -Coordinate.y=Height -local _speed=Speed or Helicopter:GetSpeedMax()*0.5 -local Route={} -local CoordinateFrom=Helicopter:GetCoordinate() -local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true) -local CoordinateTo=Coordinate -local landheight=CoordinateTo:GetLandHeight() -CoordinateTo.y=landheight+50 -local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true) -Route[#Route+1]=WaypointFrom -Route[#Route+1]=WaypointTo -Helicopter:WayPointInitialize(Route) -local Tasks={} -Tasks[#Tasks+1]=Helicopter:TaskLandAtVec2(CoordinateTo:GetVec2()) -Route[#Route].task=Helicopter:TaskCombo(Tasks) -Route[#Route+1]=WaypointTo -Helicopter:Route(Route,1) -self.PickupZone=PickupZone -self:GetParent(self,AI_CARGO_HELICOPTER).onafterPickup(self,Helicopter,From,Event,To,Coordinate,Speed,Height,PickupZone) -end -end -function AI_CARGO_HELICOPTER:_Deploy(AICargoHelicopter,Coordinate,DeployZone) -AICargoHelicopter:__Queue(-10,Coordinate,100,DeployZone) -end -function AI_CARGO_HELICOPTER:onafterDeploy(Helicopter,From,Event,To,Coordinate,Speed,Height,DeployZone) -self:F({From,Event,To,Coordinate,Speed,Height,DeployZone}) -if Helicopter and Helicopter:IsAlive()~=nil then -self.RouteDeploy=true -local Route={} -Coordinate.y=Height -local _speed=Speed or Helicopter:GetSpeedMax()*0.5 -local CoordinateFrom=Helicopter:GetCoordinate() -local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true) -Route[#Route+1]=WaypointFrom -Route[#Route+1]=WaypointFrom -local CoordinateTo=Coordinate -local landheight=CoordinateTo:GetLandHeight() -CoordinateTo.y=landheight+50 -local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true) -Route[#Route+1]=WaypointTo -Route[#Route+1]=WaypointTo -Helicopter:WayPointInitialize(Route) -local Tasks={} -Tasks[#Tasks+1]=Helicopter:TaskFunction("AI_CARGO_HELICOPTER._Deploy",self,Coordinate,DeployZone) -Tasks[#Tasks+1]=Helicopter:TaskOrbitCircle(math.random(30,100),_speed,CoordinateTo:GetRandomCoordinateInRadius(800,500)) -Route[#Route].task=Helicopter:TaskCombo(Tasks) -Route[#Route+1]=WaypointTo -Helicopter:Route(Route,0) -self:GetParent(self,AI_CARGO_HELICOPTER).onafterDeploy(self,Helicopter,From,Event,To,Coordinate,Speed,Height,DeployZone) -end -end -function AI_CARGO_HELICOPTER:onafterHome(Helicopter,From,Event,To,Coordinate,Speed,Height,HomeZone) -self:F({From,Event,To,Coordinate,Speed,Height}) -if Helicopter and Helicopter:IsAlive()~=nil then -self.RouteHome=true -local Route={} -Height=Height or 50 -Speed=Speed or Helicopter:GetSpeedMax()*0.5 -local CoordinateFrom=Helicopter:GetCoordinate() -local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,Speed,true) -Route[#Route+1]=WaypointFrom -local CoordinateTo=Coordinate -local landheight=CoordinateTo:GetLandHeight() -CoordinateTo.y=landheight+Height -local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,Speed,true) -Route[#Route+1]=WaypointTo -Helicopter:WayPointInitialize(Route) -local Tasks={} -Tasks[#Tasks+1]=Helicopter:TaskLandAtVec2(CoordinateTo:GetVec2()) -Route[#Route].task=Helicopter:TaskCombo(Tasks) -Route[#Route+1]=WaypointTo -Helicopter:Route(Route,0) -end -end -AI_CARGO_AIRPLANE={ -ClassName="AI_CARGO_AIRPLANE", -Coordinate=nil, -} -function AI_CARGO_AIRPLANE:New(Airplane,CargoSet) -local self=BASE:Inherit(self,AI_CARGO:New(Airplane,CargoSet)) -self:AddTransition("*","Landed","*") -self:AddTransition("*","Home","*") -self:AddTransition("*","Destroyed","Destroyed") -self:SetCarrier(Airplane) -return self -end -function AI_CARGO_AIRPLANE:SetCarrier(Airplane) -local AICargo=self -self.Airplane=Airplane -self.Airplane:SetState(self.Airplane,"AI_CARGO_AIRPLANE",self) -self.RoutePickup=false -self.RouteDeploy=false -Airplane:HandleEvent(EVENTS.Dead) -Airplane:HandleEvent(EVENTS.Hit) -Airplane:HandleEvent(EVENTS.EngineShutdown) -function Airplane:OnEventDead(EventData) -local AICargoTroops=self:GetState(self,"AI_CARGO_AIRPLANE") -self:F({AICargoTroops=AICargoTroops}) -if AICargoTroops then -self:F({}) -if not AICargoTroops:Is("Loaded")then -AICargoTroops:Destroyed() -end -end -end -function Airplane:OnEventHit(EventData) -local AICargoTroops=self:GetState(self,"AI_CARGO_AIRPLANE") -if AICargoTroops then -self:F({OnHitLoaded=AICargoTroops:Is("Loaded")}) -if AICargoTroops:Is("Loaded")or AICargoTroops:Is("Boarding")then -AICargoTroops:Unload() -end -end -end -function Airplane:OnEventEngineShutdown(EventData) -AICargo.Relocating=false -AICargo:Landed(self.Airplane) -end -self.Coalition=self.Airplane:GetCoalition() -self:SetControllable(Airplane) -return self -end -function AI_CARGO_AIRPLANE:FindCarrier(Coordinate,Radius) -local CoordinateZone=ZONE_RADIUS:New("Zone",Coordinate:GetVec2(),Radius) -CoordinateZone:Scan({Object.Category.UNIT}) -for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do -local NearUnit=UNIT:Find(DCSUnit) -self:F({NearUnit=NearUnit}) -if not NearUnit:GetState(NearUnit,"AI_CARGO_AIRPLANE")then -local Attributes=NearUnit:GetDesc() -self:F({Desc=Attributes}) -if NearUnit:HasAttribute("Trucks")then -self:SetCarrier(NearUnit) -break -end -end -end -end -function AI_CARGO_AIRPLANE:onafterLanded(Airplane,From,Event,To) -self:F({Airplane,From,Event,To}) -if Airplane and Airplane:IsAlive()~=nil then -if self.RoutePickup==true then -self:Load(self.PickupZone) -end -if self.RouteDeploy==true then -self:Unload() -self.RouteDeploy=false -end -end -end -function AI_CARGO_AIRPLANE:onafterPickup(Airplane,From,Event,To,Coordinate,Speed,Height,PickupZone) -if Airplane and Airplane:IsAlive()then -local airbasepickup=Coordinate:GetClosestAirbase() -self.PickupZone=PickupZone or ZONE_AIRBASE:New(airbasepickup:GetName()) -local ClosestAirbase,DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase() -if Airplane:InAir()then -self.Airbase=nil -else -self.Airbase=ClosestAirbase -end -local Airbase=self.PickupZone:GetAirbase() -local Dist=Airbase:GetCoordinate():Get2DDistance(ClosestAirbase:GetCoordinate()) -if Airplane:InAir()or Dist>500 then -self:Route(Airplane,Airbase,Speed,Height) -self.Airbase=Airbase -self.RoutePickup=true -else -self.RoutePickup=true -self:Landed() -end -self:GetParent(self,AI_CARGO_AIRPLANE).onafterPickup(self,Airplane,From,Event,To,Coordinate,Speed,Height,self.PickupZone) -end -end -function AI_CARGO_AIRPLANE:onafterDeploy(Airplane,From,Event,To,Coordinate,Speed,Height,DeployZone) -if Airplane and Airplane:IsAlive()~=nil then -local Airbase=Coordinate:GetClosestAirbase() -if DeployZone then -Airbase=DeployZone:GetAirbase() -end -if Airplane:IsAlive()==false then -Airplane:SetCommand({id='Start',params={}}) -end -self:Route(Airplane,Airbase,Speed,Height) -self.RouteDeploy=true -self.Airbase=Airbase -self:GetParent(self,AI_CARGO_AIRPLANE).onafterDeploy(self,Airplane,From,Event,To,Coordinate,Speed,Height,DeployZone) -end -end -function AI_CARGO_AIRPLANE:onafterUnload(Airplane,From,Event,To,DeployZone) -local UnboardInterval=10 -local UnboardDelay=10 -if Airplane and Airplane:IsAlive()then -for _,AirplaneUnit in pairs(Airplane:GetUnits())do -local Cargos=AirplaneUnit:GetCargo() -for CargoID,Cargo in pairs(Cargos)do -local Angle=180 -local CargoCarrierHeading=Airplane:GetHeading() -local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) -self:T({CargoCarrierHeading,CargoDeployHeading}) -local CargoDeployCoordinate=Airplane:GetPointVec2():Translate(150,CargoDeployHeading) -Cargo:__UnBoard(UnboardDelay,CargoDeployCoordinate) -UnboardDelay=UnboardDelay+UnboardInterval -Cargo:SetDeployed(true) -self:__Unboard(UnboardDelay,Cargo,AirplaneUnit,DeployZone) -end -end -end -end -function AI_CARGO_AIRPLANE:Route(Airplane,Airbase,Speed,Height,Uncontrolled) -if Airplane and Airplane:IsAlive()then -local Takeoff=SPAWN.Takeoff.Cold -local Template=Airplane:GetTemplate() -if Template==nil then -return -end -local Points={} -local AirbasePointVec2=Airbase:GetPointVec2() -local ToWaypoint=AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO,"Land","Landing",Speed or Airplane:GetSpeedMax()*0.8,true,Airbase) -if self.Airbase then -Template.route.points[2]=ToWaypoint -Airplane:RespawnAtCurrentAirbase(Template,Takeoff,Uncontrolled) -else -local GroupPoint=Airplane:GetVec2() -local FromWaypoint={} -FromWaypoint.x=GroupPoint.x -FromWaypoint.y=GroupPoint.y -FromWaypoint.type="Turning Point" -FromWaypoint.action="Turning Point" -FromWaypoint.speed=Airplane:GetSpeedMax()*0.8 -Points[1]=FromWaypoint -Points[2]=ToWaypoint -local PointVec3=Airplane:GetPointVec3() -Template.x=PointVec3.x -Template.y=PointVec3.z -Template.route.points=Points -local GroupSpawned=Airplane:Respawn(Template) -end -end -end -function AI_CARGO_AIRPLANE:onafterHome(Airplane,From,Event,To,Coordinate,Speed,Height,HomeZone) -if Airplane and Airplane:IsAlive()then -self.RouteHome=true -local HomeBase=HomeZone:GetAirbase() -self.Airbase=HomeBase -self:Route(Airplane,HomeBase,Speed,Height) -end -end -AI_CARGO_SHIP={ -ClassName="AI_CARGO_SHIP", -Coordinate=nil -} -function AI_CARGO_SHIP:New(Ship,CargoSet,CombatRadius,ShippingLane) -local self=BASE:Inherit(self,AI_CARGO:New(Ship,CargoSet)) -self:AddTransition("*","Monitor","*") -self:AddTransition("*","Destroyed","Destroyed") -self:AddTransition("*","Home","*") -self:SetCombatRadius(0) -self:SetShippingLane(ShippingLane) -self:SetCarrier(Ship) -return self -end -function AI_CARGO_SHIP:SetCarrier(CargoCarrier) -self.CargoCarrier=CargoCarrier -self.CargoCarrier:SetState(self.CargoCarrier,"AI_CARGO_SHIP",self) -CargoCarrier:HandleEvent(EVENTS.Dead) -function CargoCarrier:OnEventDead(EventData) -self:F({"dead"}) -local AICargoTroops=self:GetState(self,"AI_CARGO_SHIP") -self:F({AICargoTroops=AICargoTroops}) -if AICargoTroops then -self:F({}) -if not AICargoTroops:Is("Loaded")then -AICargoTroops:Destroyed() -end -end -end -self.Zone=ZONE_UNIT:New(self.CargoCarrier:GetName().."-Zone",self.CargoCarrier,self.CombatRadius) -self.Coalition=self.CargoCarrier:GetCoalition() -self:SetControllable(CargoCarrier) -return self -end -function AI_CARGO_SHIP:FindCarrier(Coordinate,Radius) -local CoordinateZone=ZONE_RADIUS:New("Zone",Coordinate:GetVec2(),Radius) -CoordinateZone:Scan({Object.Category.UNIT}) -for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do -local NearUnit=UNIT:Find(DCSUnit) -self:F({NearUnit=NearUnit}) -if not NearUnit:GetState(NearUnit,"AI_CARGO_SHIP")then -local Attributes=NearUnit:GetDesc() -self:F({Desc=Attributes}) -if NearUnit:HasAttributes("Trucks")then -return NearUnit:GetGroup() -end -end -end -return nil -end -function AI_CARGO_SHIP:SetShippingLane(ShippingLane) -self.ShippingLane=ShippingLane -return self -end -function AI_CARGO_SHIP:SetCombatRadius(CombatRadius) -self.CombatRadius=CombatRadius or 0 -return self -end -function AI_CARGO_SHIP:FollowToCarrier(Me,ShipUnit,CargoGroup) -local InfantryGroup=CargoGroup:GetGroup() -self:F({self=self:GetClassNameAndID(),InfantryGroup=InfantryGroup:GetName()}) -if ShipUnit:IsAlive()then -if InfantryGroup:IsPartlyInZone(ZONE_UNIT:New("Radius",ShipUnit,1000))then -Me:Guard() -else -self:F({InfantryGroup=InfantryGroup:GetName()}) -if InfantryGroup:IsAlive()then -self:F({InfantryGroup=InfantryGroup:GetName()}) -local Waypoints={} -local FromCoord=InfantryGroup:GetCoordinate() -local FromGround=FromCoord:WaypointGround(10,"Diamond") -self:F({FromGround=FromGround}) -table.insert(Waypoints,FromGround) -local ToCoord=ShipUnit:GetCoordinate():GetRandomCoordinateInRadius(10,5) -local ToGround=ToCoord:WaypointGround(10,"Diamond") -self:F({ToGround=ToGround}) -table.insert(Waypoints,ToGround) -local TaskRoute=InfantryGroup:TaskFunction("AI_CARGO_SHIP.FollowToCarrier",Me,ShipUnit,CargoGroup) -self:F({Waypoints=Waypoints}) -local Waypoint=Waypoints[#Waypoints] -InfantryGroup:SetTaskWaypoint(Waypoint,TaskRoute) -InfantryGroup:Route(Waypoints,1) -end -end -end -end -function AI_CARGO_SHIP:onafterMonitor(Ship,From,Event,To) -self:F({Ship,From,Event,To,IsTransporting=self:IsTransporting()}) -if self.CombatRadius>0 then -if Ship and Ship:IsAlive()then -if self.CarrierCoordinate then -if self:IsTransporting()==true then -local Coordinate=Ship:GetCoordinate() -if self:Is("Unloaded")or self:Is("Loaded")then -self.Zone:Scan({Object.Category.UNIT}) -if self.Zone:IsAllInZoneOfCoalition(self.Coalition)then -if self:Is("Unloaded")then -self:Reload() -end -else -if self:Is("Loaded")then -self:__Unload(1,nil,true) -else -if self:Is("Unloaded")then -end -self:F("I am here"..self:GetCurrentState()) -if self:Is("Following")then -for Cargo,ShipUnit in pairs(self.Carrier_Cargo)do -local Cargo=Cargo -local ShipUnit=ShipUnit -if Cargo:IsAlive()then -if not Cargo:IsNear(ShipUnit,40)then -ShipUnit:RouteStop() -self.CarrierStopped=true -else -if self.CarrierStopped then -if Cargo:IsNear(ShipUnit,25)then -ShipUnit:RouteResume() -self.CarrierStopped=nil -end -end -end -end -end -end -end -end -end -end -end -self.CarrierCoordinate=Ship:GetCoordinate() -end -self:__Monitor(-5) -end -end -function AI_CARGO_SHIP._Pickup(Ship,self,Coordinate,Speed,PickupZone) -Ship:F({"AI_CARGO_Ship._Pickup:",Ship:GetName()}) -if Ship:IsAlive()then -self:Load(PickupZone) -end -end -function AI_CARGO_SHIP._Deploy(Ship,self,Coordinate,DeployZone) -Ship:F({"AI_CARGO_Ship._Deploy:",Ship}) -if Ship:IsAlive()then -self:Unload(DeployZone) -end -end -function AI_CARGO_SHIP:onafterPickup(Ship,From,Event,To,Coordinate,Speed,Height,PickupZone) -if Ship and Ship:IsAlive()then -AI_CARGO_SHIP._Pickup(Ship,self,Coordinate,Speed,PickupZone) -self:GetParent(self,AI_CARGO_SHIP).onafterPickup(self,Ship,From,Event,To,Coordinate,Speed,Height,PickupZone) -end -end -function AI_CARGO_SHIP:onafterDeploy(Ship,From,Event,To,Coordinate,Speed,Height,DeployZone) -if Ship and Ship:IsAlive()then -Speed=Speed or Ship:GetSpeedMax()*0.8 -local lane=self.ShippingLane -if lane then -local Waypoints={} -for i=1,#lane do -local coord=lane[i] -local Waypoint=coord:WaypointGround(_speed) -table.insert(Waypoints,Waypoint) -end -local TaskFunction=Ship:TaskFunction("AI_CARGO_SHIP._Deploy",self,Coordinate,DeployZone) -local Waypoint=Waypoints[#Waypoints] -Ship:SetTaskWaypoint(Waypoint,TaskFunction) -Ship:Route(Waypoints,1) -self:GetParent(self,AI_CARGO_SHIP).onafterDeploy(self,Ship,From,Event,To,Coordinate,Speed,Height,DeployZone) -else -self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!") -end -end -end -function AI_CARGO_SHIP:onafterUnload(Ship,From,Event,To,DeployZone,Defend) -self:F({Ship,From,Event,To,DeployZone,Defend=Defend}) -local UnboardInterval=5 -local UnboardDelay=5 -if Ship and Ship:IsAlive()then -for _,ShipUnit in pairs(Ship:GetUnits())do -local ShipUnit=ShipUnit -Ship:RouteStop() -for _,Cargo in pairs(ShipUnit:GetCargo())do -self:F({Cargo=Cargo:GetName(),Isloaded=Cargo:IsLoaded()}) -if Cargo:IsLoaded()then -local unboardCoord=DeployZone:GetRandomPointVec2() -Cargo:__UnBoard(UnboardDelay,unboardCoord,1000) -UnboardDelay=UnboardDelay+Cargo:GetCount()*UnboardInterval -self:__Unboard(UnboardDelay,Cargo,ShipUnit,DeployZone,Defend) -if not Defend==true then -Cargo:SetDeployed(true) -end -end -end -end -end -end -function AI_CARGO_SHIP:onafterHome(Ship,From,Event,To,Coordinate,Speed,Height,HomeZone) -if Ship and Ship:IsAlive()then -self.RouteHome=true -Speed=Speed or Ship:GetSpeedMax()*0.8 -local lane=self.ShippingLane -if lane then -local Waypoints={} -for i=#lane,1,-1 do -local coord=lane[i] -local Waypoint=coord:WaypointGround(_speed) -table.insert(Waypoints,Waypoint) -end -local Waypoint=Waypoints[#Waypoints] -Ship:Route(Waypoints,1) -else -self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!") -end -end -end -AI_CARGO_DISPATCHER={ -ClassName="AI_CARGO_DISPATCHER", -AI_Cargo={}, -PickupCargo={} -} -AI_CARGO_DISPATCHER.AI_Cargo={} -AI_CARGO_DISPATCHER.PickupCargo={} -function AI_CARGO_DISPATCHER:New(CarrierSet,CargoSet,PickupZoneSet,DeployZoneSet) -local self=BASE:Inherit(self,FSM:New()) -self.SetCarrier=CarrierSet -self.SetCargo=CargoSet -self.PickupZoneSet=PickupZoneSet -self.DeployZoneSet=DeployZoneSet -self:SetStartState("Idle") -self:AddTransition("Monitoring","Monitor","Monitoring") -self:AddTransition("Idle","Start","Monitoring") -self:AddTransition("Monitoring","Stop","Idle") -self:AddTransition("Monitoring","Pickup","Monitoring") -self:AddTransition("Monitoring","Load","Monitoring") -self:AddTransition("Monitoring","Loading","Monitoring") -self:AddTransition("Monitoring","Loaded","Monitoring") -self:AddTransition("Monitoring","PickedUp","Monitoring") -self:AddTransition("Monitoring","Transport","Monitoring") -self:AddTransition("Monitoring","Deploy","Monitoring") -self:AddTransition("Monitoring","Unload","Monitoring") -self:AddTransition("Monitoring","Unloading","Monitoring") -self:AddTransition("Monitoring","Unloaded","Monitoring") -self:AddTransition("Monitoring","Deployed","Monitoring") -self:AddTransition("Monitoring","Home","Monitoring") -self:SetMonitorTimeInterval(30) -self:SetDeployRadius(500,200) -self.PickupCargo={} -self.CarrierHome={} -function self.SetCarrier.OnAfterRemoved(SetCarrier,From,Event,To,CarrierName,Carrier) -self:F({Carrier=Carrier:GetName()}) -self.PickupCargo[Carrier]=nil -self.CarrierHome[Carrier]=nil -end -return self -end -function AI_CARGO_DISPATCHER:SetMonitorTimeInterval(MonitorTimeInterval) -self.MonitorTimeInterval=MonitorTimeInterval -return self -end -function AI_CARGO_DISPATCHER:SetHomeZone(HomeZone) -self.HomeZone=HomeZone -return self -end -function AI_CARGO_DISPATCHER:SetPickupRadius(OuterRadius,InnerRadius) -OuterRadius=OuterRadius or 0 -InnerRadius=InnerRadius or OuterRadius -self.PickupOuterRadius=OuterRadius -self.PickupInnerRadius=InnerRadius -return self -end -function AI_CARGO_DISPATCHER:SetPickupSpeed(MaxSpeed,MinSpeed) -MaxSpeed=MaxSpeed or 999 -MinSpeed=MinSpeed or MaxSpeed -self.PickupMinSpeed=MinSpeed -self.PickupMaxSpeed=MaxSpeed -return self -end -function AI_CARGO_DISPATCHER:SetDeployRadius(OuterRadius,InnerRadius) -OuterRadius=OuterRadius or 0 -InnerRadius=InnerRadius or OuterRadius -self.DeployOuterRadius=OuterRadius -self.DeployInnerRadius=InnerRadius -return self -end -function AI_CARGO_DISPATCHER:SetDeploySpeed(MaxSpeed,MinSpeed) -MaxSpeed=MaxSpeed or 999 -MinSpeed=MinSpeed or MaxSpeed -self.DeployMinSpeed=MinSpeed -self.DeployMaxSpeed=MaxSpeed -return self -end -function AI_CARGO_DISPATCHER:SetPickupHeight(MaxHeight,MinHeight) -MaxHeight=MaxHeight or 200 -MinHeight=MinHeight or MaxHeight -self.PickupMinHeight=MinHeight -self.PickupMaxHeight=MaxHeight -return self -end -function AI_CARGO_DISPATCHER:SetDeployHeight(MaxHeight,MinHeight) -MaxHeight=MaxHeight or 200 -MinHeight=MinHeight or MaxHeight -self.DeployMinHeight=MinHeight -self.DeployMaxHeight=MaxHeight -return self -end -function AI_CARGO_DISPATCHER:onafterMonitor() -self:F("Carriers") -self.SetCarrier:Flush() -for CarrierGroupName,Carrier in pairs(self.SetCarrier:GetSet())do -local Carrier=Carrier -if Carrier:IsAlive()~=nil then -local AI_Cargo=self.AI_Cargo[Carrier] -if not AI_Cargo then -self.AI_Cargo[Carrier]=self:AICargo(Carrier,self.SetCargo,self.CombatRadius) -AI_Cargo=self.AI_Cargo[Carrier] -function AI_Cargo.OnAfterPickup(AI_Cargo,CarrierGroup,From,Event,To,Coordinate,Speed,Height,PickupZone) -self:Pickup(CarrierGroup,Coordinate,Speed,Height,PickupZone) -end -function AI_Cargo.OnAfterLoad(AI_Cargo,CarrierGroup,From,Event,To,PickupZone) -self:Load(CarrierGroup,PickupZone) -end -function AI_Cargo.OnAfterBoard(AI_Cargo,CarrierGroup,From,Event,To,Cargo,CarrierUnit,PickupZone) -self:Loading(CarrierGroup,Cargo,CarrierUnit,PickupZone) -end -function AI_Cargo.OnAfterLoaded(AI_Cargo,CarrierGroup,From,Event,To,Cargo,CarrierUnit,PickupZone) -self:Loaded(CarrierGroup,Cargo,CarrierUnit,PickupZone) -end -function AI_Cargo.OnAfterPickedUp(AI_Cargo,CarrierGroup,From,Event,To,PickupZone) -self:PickedUp(CarrierGroup,PickupZone) -self:Transport(CarrierGroup) -end -function AI_Cargo.OnAfterDeploy(AI_Cargo,CarrierGroup,From,Event,To,Coordinate,Speed,Height,DeployZone) -self:Deploy(CarrierGroup,Coordinate,Speed,Height,DeployZone) -end -function AI_Cargo.OnAfterUnload(AI_Cargo,Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone) -self:Unloading(Carrier,Cargo,CarrierUnit,DeployZone) -end -function AI_Cargo.OnAfterUnboard(AI_Cargo,CarrierGroup,From,Event,To,Cargo,CarrierUnit,DeployZone) -self:Unloading(CarrierGroup,Cargo,CarrierUnit,DeployZone) -end -function AI_Cargo.OnAfterUnloaded(AI_Cargo,Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone) -self:Unloaded(Carrier,Cargo,CarrierUnit,DeployZone) -end -function AI_Cargo.OnAfterDeployed(AI_Cargo,Carrier,From,Event,To,DeployZone) -self:Deployed(Carrier,DeployZone) -end -function AI_Cargo.OnAfterHome(AI_Cargo,Carrier,From,Event,To,Coordinate,Speed,Height,HomeZone) -self:Home(Carrier,Coordinate,Speed,Height,HomeZone) -end -end -self:T({Carrier=CarrierGroupName,IsRelocating=AI_Cargo:IsRelocating(),IsTransporting=AI_Cargo:IsTransporting()}) -if AI_Cargo:IsRelocating()==false and AI_Cargo:IsTransporting()==false then -local PickupCargo=nil -local PickupZone=nil -self.SetCargo:Flush() -for CargoName,Cargo in UTILS.spairs(self.SetCargo:GetSet(),function(t,a,b)return t[a]:GetWeight()=Cargo:GetWeight()then -self.PickupCargo[Carrier]=CargoCoordinate -PickupCargo=Cargo -break -else -local text=string.format("WARNING: Cargo %s is too heavy to be loaded into transport. Cargo weight %.1f > %.1f load capacity of carrier %s.", -tostring(Cargo:GetName()),Cargo:GetWeight(),LargestLoadCapacity,tostring(Carrier:GetName())) -self:I(text) -end -end -end -end -end -if PickupCargo then -self.CarrierHome[Carrier]=nil -local PickupCoordinate=PickupCargo:GetCoordinate():GetRandomCoordinateInRadius(self.PickupOuterRadius,self.PickupInnerRadius) -AI_Cargo:Pickup(PickupCoordinate,math.random(self.PickupMinSpeed,self.PickupMaxSpeed),math.random(self.PickupMinHeight,self.PickupMaxHeight),PickupZone) -break -else -if self.HomeZone then -if not self.CarrierHome[Carrier]then -self.CarrierHome[Carrier]=true -AI_Cargo:Home(self.HomeZone:GetRandomPointVec2(),math.random(self.PickupMinSpeed,self.PickupMaxSpeed),math.random(self.PickupMinHeight,self.PickupMaxHeight),self.HomeZone) -end -end -end -end -end -end -self:__Monitor(self.MonitorTimeInterval) -end -function AI_CARGO_DISPATCHER:onafterStart(From,Event,To) -self:__Monitor(-1) -end -function AI_CARGO_DISPATCHER:onafterTransport(From,Event,To,Carrier,Cargo) -if self.DeployZoneSet then -if self.AI_Cargo[Carrier]:IsTransporting()==true then -local DeployZone=self.DeployZoneSet:GetRandomZone() -local DeployCoordinate=DeployZone:GetCoordinate():GetRandomCoordinateInRadius(self.DeployOuterRadius,self.DeployInnerRadius) -self.AI_Cargo[Carrier]:__Deploy(0.1,DeployCoordinate,math.random(self.DeployMinSpeed,self.DeployMaxSpeed),math.random(self.DeployMinHeight,self.DeployMaxHeight),DeployZone) -end -end -self:F({Carrier=Carrier:GetName(),PickupCargo=self.PickupCargo}) -self.PickupCargo[Carrier]=nil -end -AI_CARGO_DISPATCHER_APC={ -ClassName="AI_CARGO_DISPATCHER_APC", -} -function AI_CARGO_DISPATCHER_APC:New(APCSet,CargoSet,PickupZoneSet,DeployZoneSet,CombatRadius) -local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(APCSet,CargoSet,PickupZoneSet,DeployZoneSet)) -self:SetDeploySpeed(120,70) -self:SetPickupSpeed(120,70) -self:SetPickupRadius(0,0) -self:SetDeployRadius(0,0) -self:SetPickupHeight() -self:SetDeployHeight() -self:SetCombatRadius(CombatRadius) -return self -end -function AI_CARGO_DISPATCHER_APC:AICargo(APC,CargoSet) -local aicargoapc=AI_CARGO_APC:New(APC,CargoSet,self.CombatRadius) -aicargoapc:SetDeployOffRoad(self.deployOffroad,self.deployFormation) -aicargoapc:SetPickupOffRoad(self.pickupOffroad,self.pickupFormation) -return aicargoapc -end -function AI_CARGO_DISPATCHER_APC:SetCombatRadius(CombatRadius) -self.CombatRadius=CombatRadius or 0 -return self -end -function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad,Formation) -self:SetPickupOffRoad(Offroad,Formation) -self:SetDeployOffRoad(Offroad,Formation) -return self -end -function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad,Formation) -self.pickupOffroad=Offroad -self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad -return self -end -function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad,Formation) -self.deployOffroad=Offroad -self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad -return self -end -AI_CARGO_DISPATCHER_HELICOPTER={ -ClassName="AI_CARGO_DISPATCHER_HELICOPTER", -} -function AI_CARGO_DISPATCHER_HELICOPTER:New(HelicopterSet,CargoSet,PickupZoneSet,DeployZoneSet) -local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(HelicopterSet,CargoSet,PickupZoneSet,DeployZoneSet)) -self:SetPickupSpeed(350,150) -self:SetDeploySpeed(350,150) -self:SetPickupRadius(40,12) -self:SetDeployRadius(40,12) -self:SetPickupHeight(500,200) -self:SetDeployHeight(500,200) -return self -end -function AI_CARGO_DISPATCHER_HELICOPTER:AICargo(Helicopter,CargoSet) -local dispatcher=AI_CARGO_HELICOPTER:New(Helicopter,CargoSet) -dispatcher:SetLandingSpeedAndHeight(27,6) -return dispatcher -end -AI_CARGO_DISPATCHER_AIRPLANE={ -ClassName="AI_CARGO_DISPATCHER_AIRPLANE", -} -function AI_CARGO_DISPATCHER_AIRPLANE:New(AirplaneSet,CargoSet,PickupZoneSet,DeployZoneSet) -local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(AirplaneSet,CargoSet,PickupZoneSet,DeployZoneSet)) -self:SetPickupSpeed(1200,600) -self:SetDeploySpeed(1200,600) -self:SetPickupRadius(0,0) -self:SetDeployRadius(0,0) -self:SetPickupHeight(8000,6000) -self:SetDeployHeight(8000,6000) -self:SetMonitorTimeInterval(600) -return self -end -function AI_CARGO_DISPATCHER_AIRPLANE:AICargo(Airplane,CargoSet) -return AI_CARGO_AIRPLANE:New(Airplane,CargoSet) -end -AI_CARGO_DISPATCHER_SHIP={ -ClassName="AI_CARGO_DISPATCHER_SHIP" -} -function AI_CARGO_DISPATCHER_SHIP:New(ShipSet,CargoSet,PickupZoneSet,DeployZoneSet,ShippingLane) -local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(ShipSet,CargoSet,PickupZoneSet,DeployZoneSet)) -self:SetPickupSpeed(60,10) -self:SetDeploySpeed(60,10) -self:SetPickupRadius(500,6000) -self:SetDeployRadius(500,6000) -self:SetPickupHeight(0,0) -self:SetDeployHeight(0,0) -self:SetShippingLane(ShippingLane) -self:SetMonitorTimeInterval(600) -return self -end -function AI_CARGO_DISPATCHER_SHIP:SetShippingLane(ShippingLane) -self.ShippingLane=ShippingLane -return self -end -function AI_CARGO_DISPATCHER_SHIP:AICargo(Ship,CargoSet) -return AI_CARGO_SHIP:New(Ship,CargoSet,0,self.ShippingLane) -end -do -ACT_ASSIGN={ -ClassName="ACT_ASSIGN", -} -function ACT_ASSIGN:New() -local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ASSIGN")) -self:AddTransition("UnAssigned","Start","Waiting") -self:AddTransition("Waiting","Assign","Assigned") -self:AddTransition("Waiting","Reject","Rejected") -self:AddTransition("*","Fail","Failed") -self:AddEndState("Assigned") -self:AddEndState("Rejected") -self:AddEndState("Failed") -self:SetStartState("UnAssigned") -return self -end -end -do -ACT_ASSIGN_ACCEPT={ -ClassName="ACT_ASSIGN_ACCEPT", -} -function ACT_ASSIGN_ACCEPT:New(TaskBriefing) -local self=BASE:Inherit(self,ACT_ASSIGN:New()) -self.TaskBriefing=TaskBriefing -return self -end -function ACT_ASSIGN_ACCEPT:Init(FsmAssign) -self.TaskBriefing=FsmAssign.TaskBriefing -end -function ACT_ASSIGN_ACCEPT:onafterStart(ProcessUnit,Task,From,Event,To) -self:__Assign(1) -end -function ACT_ASSIGN_ACCEPT:onenterAssigned(ProcessUnit,Task,From,Event,To,TaskGroup) -self.Task:Assign(ProcessUnit,ProcessUnit:GetPlayerName()) -end -end -do -ACT_ASSIGN_MENU_ACCEPT={ -ClassName="ACT_ASSIGN_MENU_ACCEPT", -} -function ACT_ASSIGN_MENU_ACCEPT:New(TaskBriefing) -local self=BASE:Inherit(self,ACT_ASSIGN:New()) -self.TaskBriefing=TaskBriefing -return self -end -function ACT_ASSIGN_MENU_ACCEPT:Init(TaskBriefing) -self.TaskBriefing=TaskBriefing -return self -end -function ACT_ASSIGN_MENU_ACCEPT:onafterStart(ProcessUnit,Task,From,Event,To) -self:GetCommandCenter():MessageToGroup("Task "..self.Task:GetName().." has been assigned to you and your group!\nRead the briefing and use the Radio Menu (F10) / Task ... CONFIRMATION menu to accept or reject the task.\nYou have 2 minutes to accept, or the task assignment will be cancelled!",ProcessUnit:GetGroup(),120) -local TaskGroup=ProcessUnit:GetGroup() -self.Menu=MENU_GROUP:New(TaskGroup,"Task "..self.Task:GetName().." CONFIRMATION") -self.MenuAcceptTask=MENU_GROUP_COMMAND:New(TaskGroup,"Accept task "..self.Task:GetName(),self.Menu,self.MenuAssign,self,TaskGroup) -self.MenuRejectTask=MENU_GROUP_COMMAND:New(TaskGroup,"Reject task "..self.Task:GetName(),self.Menu,self.MenuReject,self,TaskGroup) -self:__Reject(120,TaskGroup) -end -function ACT_ASSIGN_MENU_ACCEPT:MenuAssign(TaskGroup) -self:__Assign(-1,TaskGroup) -end -function ACT_ASSIGN_MENU_ACCEPT:MenuReject(TaskGroup) -self:__Reject(-1,TaskGroup) -end -function ACT_ASSIGN_MENU_ACCEPT:onafterAssign(ProcessUnit,Task,From,Event,To,TaskGroup) -self.Menu:Remove() -end -function ACT_ASSIGN_MENU_ACCEPT:onafterReject(ProcessUnit,Task,From,Event,To,TaskGroup) -self:F({TaskGroup=TaskGroup}) -self.Menu:Remove() -self.Task:RejectGroup(TaskGroup) -end -function ACT_ASSIGN_MENU_ACCEPT:onenterAssigned(ProcessUnit,Task,From,Event,To,TaskGroup) -self.Task:Assign(ProcessUnit,ProcessUnit:GetPlayerName()) -end -end -do -ACT_ROUTE={ -ClassName="ACT_ROUTE", -} -function ACT_ROUTE:New() -local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ROUTE")) -self:AddTransition("*","Reset","None") -self:AddTransition("None","Start","Routing") -self:AddTransition("*","Report","*") -self:AddTransition("Routing","Route","Routing") -self:AddTransition("Routing","Pause","Pausing") -self:AddTransition("Routing","Arrive","Arrived") -self:AddTransition("*","Cancel","Cancelled") -self:AddTransition("Arrived","Success","Success") -self:AddTransition("*","Fail","Failed") -self:AddTransition("","","") -self:AddTransition("","","") -self:AddEndState("Arrived") -self:AddEndState("Failed") -self:AddEndState("Cancelled") -self:SetStartState("None") -self:SetRouteMode("C") -return self -end -function ACT_ROUTE:SetMenuCancel(MenuGroup,MenuText,ParentMenu,MenuTime,MenuTag) -self.CancelMenuGroupCommand=MENU_GROUP_COMMAND:New( -MenuGroup, -MenuText, -ParentMenu, -self.MenuCancel, -self -):SetTime(MenuTime):SetTag(MenuTag) -ParentMenu:SetTime(MenuTime) -ParentMenu:Remove(MenuTime,MenuTag) -return self -end -function ACT_ROUTE:SetRouteMode(RouteMode) -self.RouteMode=RouteMode -return self -end -function ACT_ROUTE:GetRouteText(Controllable) -local RouteText="" -local Coordinate=nil -if self.Coordinate then -Coordinate=self.Coordinate -end -if self.Zone then -Coordinate=self.Zone:GetPointVec3(self.Altitude) -Coordinate:SetHeading(self.Heading) -end -local Task=self:GetTask() -local CC=self:GetTask():GetMission():GetCommandCenter() -if CC then -if CC:IsModeWWII()then -local ShortestDistance=0 -local ShortestReferencePoint=nil -local ShortestReferenceName="" -self:F({CC.ReferencePoints}) -for ZoneName,Zone in pairs(CC.ReferencePoints)do -self:F({ZoneName=ZoneName}) -local Zone=Zone -local ZoneCoord=Zone:GetCoordinate() -local ZoneDistance=ZoneCoord:Get2DDistance(Coordinate) -self:F({ShortestDistance,ShortestReferenceName}) -if ShortestDistance==0 or ZoneDistance=self.DisplayInterval then -self:T({HasArrived=HasArrived}) -if not HasArrived then -self:Report() -end -self.DisplayCount=1 -else -self.DisplayCount=self.DisplayCount+1 -end -if HasArrived then -self:__Arrive(1) -else -self:__Route(1) -end -return HasArrived -end -return false -end -end -do -ACT_ROUTE_POINT={ -ClassName="ACT_ROUTE_POINT", -} -function ACT_ROUTE_POINT:New(Coordinate,Range) -local self=BASE:Inherit(self,ACT_ROUTE:New()) -self.Coordinate=Coordinate -self.Range=Range or 0 -self.DisplayInterval=30 -self.DisplayCount=30 -self.DisplayMessage=true -self.DisplayTime=10 -return self -end -function ACT_ROUTE_POINT:Init(FsmRoute) -self.Coordinate=FsmRoute.Coordinate -self.Range=FsmRoute.Range or 0 -self.DisplayInterval=30 -self.DisplayCount=30 -self.DisplayMessage=true -self.DisplayTime=10 -self:SetStartState("None") -end -function ACT_ROUTE_POINT:SetCoordinate(Coordinate) -self:F2({Coordinate}) -self.Coordinate=Coordinate -end -function ACT_ROUTE_POINT:GetCoordinate() -self:F2({self.Coordinate}) -return self.Coordinate -end -function ACT_ROUTE_POINT:SetRange(Range) -self:F2({Range}) -self.Range=Range or 10000 -end -function ACT_ROUTE_POINT:GetRange() -self:F2({self.Range}) -return self.Range -end -function ACT_ROUTE_POINT:onfuncHasArrived(ProcessUnit) -if ProcessUnit:IsAlive()then -local Distance=self.Coordinate:Get2DDistance(ProcessUnit:GetCoordinate()) -if Distance<=self.Range then -local RouteText="Task \""..self:GetTask():GetName().."\", you have arrived." -self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) -return true -end -end -return false -end -function ACT_ROUTE_POINT:onafterReport(ProcessUnit,From,Event,To) -local RouteText="Task \""..self:GetTask():GetName().."\", "..self:GetRouteText(ProcessUnit) -self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Update) -end -end -do -ACT_ROUTE_ZONE={ -ClassName="ACT_ROUTE_ZONE", -} -function ACT_ROUTE_ZONE:New(Zone) -local self=BASE:Inherit(self,ACT_ROUTE:New()) -self.Zone=Zone -self.DisplayInterval=30 -self.DisplayCount=30 -self.DisplayMessage=true -self.DisplayTime=10 -return self -end -function ACT_ROUTE_ZONE:Init(FsmRoute) -self.Zone=FsmRoute.Zone -self.DisplayInterval=30 -self.DisplayCount=30 -self.DisplayMessage=true -self.DisplayTime=10 -end -function ACT_ROUTE_ZONE:SetZone(Zone,Altitude,Heading) -self.Zone=Zone -self.Altitude=Altitude -self.Heading=Heading -end -function ACT_ROUTE_ZONE:GetZone() -return self.Zone -end -function ACT_ROUTE_ZONE:onfuncHasArrived(ProcessUnit) -if ProcessUnit:IsInZone(self.Zone)then -local RouteText="Task \""..self:GetTask():GetName().."\", you have arrived within the zone." -self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) -end -return ProcessUnit:IsInZone(self.Zone) -end -function ACT_ROUTE_ZONE:onafterReport(ProcessUnit,From,Event,To) -self:F({ProcessUnit=ProcessUnit}) -local RouteText="Task \""..self:GetTask():GetName().."\", "..self:GetRouteText(ProcessUnit) -self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Update) -end -end -do -ACT_ACCOUNT={ -ClassName="ACT_ACCOUNT", -TargetSetUnit=nil, -} -function ACT_ACCOUNT:New() -local self=BASE:Inherit(self,FSM_PROCESS:New()) -self:AddTransition("Assigned","Start","Waiting") -self:AddTransition("*","Wait","Waiting") -self:AddTransition("*","Report","Report") -self:AddTransition("*","Event","Account") -self:AddTransition("Account","Player","AccountForPlayer") -self:AddTransition("Account","Other","AccountForOther") -self:AddTransition({"Account","AccountForPlayer","AccountForOther"},"More","Wait") -self:AddTransition({"Account","AccountForPlayer","AccountForOther"},"NoMore","Accounted") -self:AddTransition("*","Fail","Failed") -self:AddEndState("Failed") -self:SetStartState("Assigned") -return self -end -function ACT_ACCOUNT:onafterStart(ProcessUnit,From,Event,To) -self:HandleEvent(EVENTS.Dead,self.onfuncEventDead) -self:HandleEvent(EVENTS.Crash,self.onfuncEventCrash) -self:HandleEvent(EVENTS.Hit) -self:__Wait(1) -end -function ACT_ACCOUNT:onenterWaiting(ProcessUnit,From,Event,To) -if self.DisplayCount>=self.DisplayInterval then -self:Report() -self.DisplayCount=1 -else -self.DisplayCount=self.DisplayCount+1 -end -return true -end -function ACT_ACCOUNT:onafterEvent(ProcessUnit,From,Event,To,Event) -self:__NoMore(1) -end -end -do -ACT_ACCOUNT_DEADS={ -ClassName="ACT_ACCOUNT_DEADS", -} -function ACT_ACCOUNT_DEADS:New() -local self=BASE:Inherit(self,ACT_ACCOUNT:New()) -self.DisplayInterval=30 -self.DisplayCount=30 -self.DisplayMessage=true -self.DisplayTime=10 -self.DisplayCategory="HQ" -return self -end -function ACT_ACCOUNT_DEADS:Init(FsmAccount) -self.Task=self:GetTask() -self.TaskName=self.Task:GetName() -end -function ACT_ACCOUNT_DEADS:onenterReport(ProcessUnit,Task,From,Event,To) -local MessageText="Your group with assigned "..self.TaskName.." task has "..Task.TargetSetUnit:GetUnitTypesText().." targets left to be destroyed." -self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) -end -function ACT_ACCOUNT_DEADS:onafterEvent(ProcessUnit,Task,From,Event,To,EventData) -self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData}) -if Task.TargetSetUnit:FindUnit(EventData.IniUnitName)then -local PlayerName=ProcessUnit:GetPlayerName() -local PlayerHit=self.PlayerHits and self.PlayerHits[EventData.IniUnitName] -if PlayerHit==PlayerName then -self:Player(EventData) -else -self:Other(EventData) -end -end -end -function ACT_ACCOUNT_DEADS:onenterAccountForPlayer(ProcessUnit,Task,From,Event,To,EventData) -self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData}) -local TaskGroup=ProcessUnit:GetGroup() -Task.TargetSetUnit:Remove(EventData.IniUnitName) -local MessageText="You have destroyed a target.\nYour group assigned with task "..self.TaskName.." has\n"..Task.TargetSetUnit:Count().." targets ( "..Task.TargetSetUnit:GetUnitTypesText().." ) left to be destroyed." -self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) -local PlayerName=ProcessUnit:GetPlayerName() -Task:AddProgress(PlayerName,"Destroyed "..EventData.IniTypeName,timer.getTime(),1) -if Task.TargetSetUnit:Count()>0 then -self:__More(1) -else -self:__NoMore(1) -end -end -function ACT_ACCOUNT_DEADS:onenterAccountForOther(ProcessUnit,Task,From,Event,To,EventData) -self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData}) -local TaskGroup=ProcessUnit:GetGroup() -Task.TargetSetUnit:Remove(EventData.IniUnitName) -local MessageText="One of the task targets has been destroyed.\nYour group assigned with task "..self.TaskName.." has\n"..Task.TargetSetUnit:Count().." targets ( "..Task.TargetSetUnit:GetUnitTypesText().." ) left to be destroyed." -self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) -if Task.TargetSetUnit:Count()>0 then -self:__More(1) -else -self:__NoMore(1) -end -end -function ACT_ACCOUNT_DEADS:OnEventHit(EventData) -self:T({"EventDead",EventData}) -if EventData.IniPlayerName and EventData.TgtDCSUnitName then -self.PlayerHits=self.PlayerHits or{} -self.PlayerHits[EventData.TgtDCSUnitName]=EventData.IniPlayerName -end -end -function ACT_ACCOUNT_DEADS:onfuncEventDead(EventData) -self:T({"EventDead",EventData}) -if EventData.IniDCSUnit then -self:Event(EventData) -end -end -function ACT_ACCOUNT_DEADS:onfuncEventCrash(EventData) -self:T({"EventDead",EventData}) -if EventData.IniDCSUnit then -self:Event(EventData) -end -end -end -do -ACT_ASSIST={ -ClassName="ACT_ASSIST", -} -function ACT_ASSIST:New() -local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ASSIST")) -self:AddTransition("None","Start","AwaitSmoke") -self:AddTransition("AwaitSmoke","Next","Smoking") -self:AddTransition("Smoking","Next","AwaitSmoke") -self:AddTransition("*","Stop","Success") -self:AddTransition("*","Fail","Failed") -self:AddEndState("Failed") -self:AddEndState("Success") -self:SetStartState("None") -return self -end -function ACT_ASSIST:onafterStart(ProcessUnit,From,Event,To) -local ProcessGroup=ProcessUnit:GetGroup() -local MissionMenu=self:GetMission():GetMenu(ProcessGroup) -local function MenuSmoke(MenuParam) -local self=MenuParam.self -local SmokeColor=MenuParam.SmokeColor -self.SmokeColor=SmokeColor -self:__Next(1) -end -self.Menu=MENU_GROUP:New(ProcessGroup,"Target acquisition",MissionMenu) -self.MenuSmokeBlue=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop blue smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Blue}) -self.MenuSmokeGreen=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop green smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Green}) -self.MenuSmokeOrange=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop Orange smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Orange}) -self.MenuSmokeRed=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop Red smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Red}) -self.MenuSmokeWhite=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop White smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.White}) -end -function ACT_ASSIST:onafterStop(ProcessUnit,From,Event,To) -self.Menu:Remove() -end -end -do -ACT_ASSIST_SMOKE_TARGETS_ZONE={ -ClassName="ACT_ASSIST_SMOKE_TARGETS_ZONE", -} -function ACT_ASSIST_SMOKE_TARGETS_ZONE:New(TargetSetUnit,TargetZone) -local self=BASE:Inherit(self,ACT_ASSIST:New()) -self.TargetSetUnit=TargetSetUnit -self.TargetZone=TargetZone -return self -end -function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init(FsmSmoke) -self.TargetSetUnit=FsmSmoke.TargetSetUnit -self.TargetZone=FsmSmoke.TargetZone -end -function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init(TargetSetUnit,TargetZone) -self.TargetSetUnit=TargetSetUnit -self.TargetZone=TargetZone -return self -end -function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking(ProcessUnit,From,Event,To) -self.TargetSetUnit:ForEachUnit( -function(SmokeUnit) -if math.random(1,(100*self.TargetSetUnit:Count())/4)<=100 then -SCHEDULER:New(self, -function() -if SmokeUnit:IsAlive()then -SmokeUnit:Smoke(self.SmokeColor,150) -end -end,{},math.random(10,60) -) -end -end -) -end -end -do -USERSOUND={ -ClassName="USERSOUND", -} -function USERSOUND:New(UserSoundFileName) -local self=BASE:Inherit(self,BASE:New()) -self.UserSoundFileName=UserSoundFileName -return self -end -function USERSOUND:SetFileName(UserSoundFileName) -self.UserSoundFileName=UserSoundFileName -return self -end -function USERSOUND:ToAll() -trigger.action.outSound(self.UserSoundFileName) -return self -end -function USERSOUND:ToCoalition(Coalition) -trigger.action.outSoundForCoalition(Coalition,self.UserSoundFileName) -return self -end -function USERSOUND:ToCountry(Country) -trigger.action.outSoundForCountry(Country,self.UserSoundFileName) -return self -end -function USERSOUND:ToGroup(Group,Delay) -Delay=Delay or 0 -if Delay>0 then -SCHEDULER:New(nil,USERSOUND.ToGroup,{self,Group},Delay) -else -trigger.action.outSoundForGroup(Group:GetID(),self.UserSoundFileName) -end -return self -end -function USERSOUND:ToUnit(Unit,Delay) -Delay=Delay or 0 -if Delay>0 then -SCHEDULER:New(nil,USERSOUND.ToUnit,{self,Unit},Delay) -else -trigger.action.outSoundForUnit(Unit:GetID(),self.UserSoundFileName) -end -return self -end -function USERSOUND:ToClient(Client,Delay) -Delay=Delay or 0 -if Delay>0 then -SCHEDULER:New(nil,USERSOUND.ToClient,{self,Client},Delay) -else -trigger.action.outSoundForUnit(Client:GetID(),self.UserSoundFileName) -end -return self -end -end -do -SOUNDBASE={ -ClassName="SOUNDBASE", -} -function SOUNDBASE:New() -local self=BASE:Inherit(self,BASE:New()) -return self -end -function SOUNDBASE:GetSpeechTime(length,speed,isGoogle) -local maxRateRatio=3 -speed=speed or 1.0 -isGoogle=isGoogle or false -local speedFactor=1.0 -if isGoogle then -speedFactor=speed -else -if speed~=0 then -speedFactor=math.abs(speed)*(maxRateRatio-1)/10+1 -end -if speed<0 then -speedFactor=1/speedFactor -end -end -local wpm=math.ceil(100*speedFactor) -local cps=math.floor((wpm*5)/60) -if type(length)=="string"then -length=string.len(length) -end -return math.ceil(length/cps) -end -end -do -SOUNDFILE={ -ClassName="SOUNDFILE", -filename=nil, -path="l10n/DEFAULT/", -duration=3, -subtitle=nil, -subduration=0, -useSRS=false, -} -function SOUNDFILE:New(FileName,Path,Duration,UseSrs) -local self=BASE:Inherit(self,BASE:New()) -self:F({FileName,Path,Duration,UseSrs}) -self:SetFileName(FileName) -self:SetPlayWithSRS(UseSrs or false) -self:SetPath(Path) -self:SetDuration(Duration) -return self -end -function SOUNDFILE:SetPath(Path) -self:F({Path}) -if not Path then -if self.useSRS then -self.path=lfs.tempdir().."Mission\\l10n\\DEFAULT" -else -self.path="l10n/DEFAULT/" -end -else -self.path=Path -end -local nmax=1000;local n=1 -while(self.path:sub(-1)=="/"or self.path:sub(-1)==[[\]])and n<=nmax do -self.path=self.path:sub(1,#self.path-1) -n=n+1 -end -self.path=self.path.."/" -self:T("self.path="..self.path) -return self -end -function SOUNDFILE:GetPath() -local path=self.path or"l10n/DEFAULT/" -return path -end -function SOUNDFILE:SetFileName(FileName) -self.filename=FileName or"Hello World.mp3" -return self -end -function SOUNDFILE:GetFileName() -return self.filename -end -function SOUNDFILE:SetDuration(Duration) -self.duration=Duration or 3 -return self -end -function SOUNDFILE:GetDuration() -return self.duration or 3 -end -function SOUNDFILE:GetName() -local path=self:GetPath() -local filename=self:GetFileName() -local name=string.format("%s%s",path,filename) -return name -end -function SOUNDFILE:SetPlayWithSRS(Switch) -self:F({Switch}) -if Switch==true or Switch==nil then -self.useSRS=true -else -self.useSRS=false -end -self:T("self.useSRS="..tostring(self.useSRS)) -return self -end -end -do -SOUNDTEXT={ -ClassName="SOUNDTEXT", -} -function SOUNDTEXT:New(Text,Duration) -local self=BASE:Inherit(self,BASE:New()) -self:SetText(Text) -self:SetDuration(Duration or STTS.getSpeechTime(Text)) -self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec",self.text,self.duration)) -return self -end -function SOUNDTEXT:SetText(Text) -self.text=Text or"Hello World!" -return self -end -function SOUNDTEXT:SetDuration(Duration) -self.duration=Duration or 3 -return self -end -function SOUNDTEXT:SetGender(Gender) -self.gender=Gender or"female" -return self -end -function SOUNDTEXT:SetCulture(Culture) -self.culture=Culture or"en-GB" -return self -end -function SOUNDTEXT:SetVoice(VoiceName) -self.voice=VoiceName -return self -end -end -RADIO={ -ClassName="RADIO", -FileName="", -Frequency=0, -Modulation=radio.modulation.AM, -Subtitle="", -SubtitleDuration=0, -Power=100, -Loop=false, -alias=nil, -} -function RADIO:New(Positionable) -local self=BASE:Inherit(self,BASE:New()) -self:F(Positionable) -if Positionable:GetPointVec2()then -self.Positionable=Positionable -return self -end -self:E({error="The passed positionable is invalid, no RADIO created!",positionable=Positionable}) -return nil -end -function RADIO:SetAlias(alias) -self.alias=tostring(alias) -return self -end -function RADIO:GetAlias() -return tostring(self.alias) -end -function RADIO:SetFileName(FileName) -self:F2(FileName) -if type(FileName)=="string"then -if FileName:find(".ogg")or FileName:find(".wav")then -if not FileName:find("l10n/DEFAULT/")then -FileName="l10n/DEFAULT/"..FileName -end -self.FileName=FileName -return self -end -end -self:E({"File name invalid. Maybe something wrong with the extension?",FileName}) -return self -end -function RADIO:SetFrequency(Frequency) -self:F2(Frequency) -if type(Frequency)=="number"then -self.Frequency=Frequency*1000000 -if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then -local commandSetFrequency={ -id="SetFrequency", -params={ -frequency=self.Frequency, -modulation=self.Modulation, -} -} -self:T2(commandSetFrequency) -self.Positionable:SetCommand(commandSetFrequency) -end -return self -end -self:E({"Frequency is not a number. Frequency unchanged.",Frequency}) -return self -end -function RADIO:SetModulation(Modulation) -self:F2(Modulation) -if type(Modulation)=="number"then -if Modulation==radio.modulation.AM or Modulation==radio.modulation.FM then -self.Modulation=Modulation -return self -end -end -self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.",self.Modulation}) -return self -end -function RADIO:SetPower(Power) -self:F2(Power) -if type(Power)=="number"then -self.Power=math.floor(math.abs(Power)) -else -self:E({"Power is invalid. Power unchanged.",self.Power}) -end -return self -end -function RADIO:SetLoop(Loop) -self:F2(Loop) -if type(Loop)=="boolean"then -self.Loop=Loop -return self -end -self:E({"Loop is invalid. Loop unchanged.",self.Loop}) -return self -end -function RADIO:SetSubtitle(Subtitle,SubtitleDuration) -self:F2({Subtitle,SubtitleDuration}) -if type(Subtitle)=="string"then -self.Subtitle=Subtitle -else -self.Subtitle="" -self:E({"Subtitle is invalid. Subtitle reset.",self.Subtitle}) -end -if type(SubtitleDuration)=="number"then -self.SubtitleDuration=SubtitleDuration -else -self.SubtitleDuration=0 -self:E({"SubtitleDuration is invalid. SubtitleDuration reset.",self.SubtitleDuration}) -end -return self -end -function RADIO:NewGenericTransmission(FileName,Frequency,Modulation,Power,Loop) -self:F({FileName,Frequency,Modulation,Power}) -self:SetFileName(FileName) -if Frequency then self:SetFrequency(Frequency)end -if Modulation then self:SetModulation(Modulation)end -if Power then self:SetPower(Power)end -if Loop then self:SetLoop(Loop)end -return self -end -function RADIO:NewUnitTransmission(FileName,Subtitle,SubtitleDuration,Frequency,Modulation,Loop) -self:F({FileName,Subtitle,SubtitleDuration,Frequency,Modulation,Loop}) -self:SetFileName(FileName) -if Modulation then -self:SetModulation(Modulation) -end -if Frequency then -self:SetFrequency(Frequency) -end -if Subtitle then -self:SetSubtitle(Subtitle,SubtitleDuration or 0) -end -if Loop then -self:SetLoop(Loop) -end -return self -end -function RADIO:Broadcast(viatrigger) -self:F({viatrigger=viatrigger}) -if(self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP")and(not viatrigger)then -self:T("Broadcasting from a UNIT or a GROUP") -local commandTransmitMessage={ -id="TransmitMessage", -params={ -file=self.FileName, -duration=self.SubtitleDuration, -subtitle=self.Subtitle, -loop=self.Loop, -}} -self:T3(commandTransmitMessage) -self.Positionable:SetCommand(commandTransmitMessage) -else -self:T("Broadcasting from a POSITIONABLE") -trigger.action.radioTransmission(self.FileName,self.Positionable:GetPositionVec3(),self.Modulation,self.Loop,self.Frequency,self.Power,tostring(self.ID)) -end -return self -end -function RADIO:StopBroadcast() -self:F() -if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then -local commandStopTransmission={id="StopTransmission",params={}} -self.Positionable:SetCommand(commandStopTransmission) -else -trigger.action.stopRadioTransmission(tostring(self.ID)) -end -return self -end -RADIOQUEUE={ -ClassName="RADIOQUEUE", -Debugmode=nil, -lid=nil, -frequency=nil, -modulation=nil, -scheduler=nil, -RQid=nil, -queue={}, -alias=nil, -dt=nil, -delay=nil, -Tlast=nil, -sendercoord=nil, -sendername=nil, -senderinit=nil, -power=nil, -numbers={}, -checking=nil, -schedonce=false, -} -function RADIOQUEUE:New(frequency,modulation,alias) -local self=BASE:Inherit(self,BASE:New()) -self.alias=alias or"My Radio" -self.lid=string.format("RADIOQUEUE %s | ",self.alias) -if frequency==nil then -self:E(self.lid.."ERROR: No frequency specified as first parameter!") -return nil -end -self.frequency=frequency*1000000 -self.modulation=modulation or radio.modulation.AM -self:SetRadioPower() -self.scheduler=SCHEDULER:New() -self.scheduler:NoTrace() -return self -end -function RADIOQUEUE:Start(delay,dt) -self.delay=delay or 1 -self.dt=dt or 0.01 -self:I(self.lid..string.format("Starting RADIOQUEUE %s on Frequency %.2f MHz [modulation=%d] in %.1f seconds (dt=%.3f sec)",self.alias,self.frequency/1000000,self.modulation,self.delay,self.dt)) -if self.schedonce then -self:_CheckRadioQueueDelayed(self.delay) -else -self.RQid=self.scheduler:Schedule(nil,RADIOQUEUE._CheckRadioQueue,{self},self.delay,self.dt) -end -return self -end -function RADIOQUEUE:Stop() -self:I(self.lid.."Stopping RADIOQUEUE.") -self.scheduler:Stop(self.RQid) -self.queue={} -return self -end -function RADIOQUEUE:SetSenderCoordinate(coordinate) -self.sendercoord=coordinate -return self -end -function RADIOQUEUE:SetSenderUnitName(name) -self.sendername=name -return self -end -function RADIOQUEUE:SetRadioPower(power) -self.power=power or 100 -return self -end -function RADIOQUEUE:SetSRS(PathToSRS,Port) -local path=PathToSRS or MSRS.path -local port=Port or MSRS.port -self.msrs=MSRS:New(path,self.frequency/1000000,self.modulation) -self.msrs:SetPort(port) -return self -end -function RADIOQUEUE:SetDigit(digit,filename,duration,path,subtitle,subduration) -local transmission={} -transmission.filename=filename -transmission.duration=duration -transmission.path=path or"l10n/DEFAULT/" -transmission.subtitle=nil -transmission.subduration=nil -if type(digit)=="number"then -digit=tostring(digit) -end -self.numbers[digit]=transmission -return self -end -function RADIOQUEUE:AddTransmission(transmission) -self:F({transmission=transmission}) -transmission.isplaying=false -transmission.Tstarted=nil -table.insert(self.queue,transmission) -if self.schedonce and not self.checking then -self:_CheckRadioQueueDelayed() -end -return self -end -function RADIOQUEUE:NewTransmission(filename,duration,path,tstart,interval,subtitle,subduration) -if not filename then -self:E(self.lid.."ERROR: No filename specified.") -return nil -end -if type(filename)~="string"then -self:E(self.lid.."ERROR: Filename specified is NOT a string.") -return nil -end -if not duration then -self:E(self.lid.."ERROR: No duration of transmission specified.") -return nil -end -if type(duration)~="number"then -self:E(self.lid.."ERROR: Duration specified is NOT a number.") -return nil -end -local transmission={} -transmission.filename=filename -transmission.duration=duration -transmission.path=path or"l10n/DEFAULT/" -transmission.Tplay=tstart or timer.getAbsTime() -transmission.subtitle=subtitle -transmission.interval=interval or 0 -if transmission.subtitle then -transmission.subduration=subduration or 5 -else -transmission.subduration=nil -end -self:AddTransmission(transmission) -return transmission -end -function RADIOQUEUE:AddSoundFile(soundfile,tstart,interval) -local transmission=self:NewTransmission(soundfile:GetFileName(),soundfile.duration,soundfile:GetPath(),tstart,interval,soundfile.subtitle,soundfile.subduration) -transmission.soundfile=soundfile -return self -end -function RADIOQUEUE:AddSoundText(soundtext,tstart,interval) -local transmission=self:NewTransmission("SoundText.ogg",soundtext.duration,nil,tstart,interval,soundtext.subtitle,soundtext.subduration) -transmission.soundtext=soundtext -return self -end -function RADIOQUEUE:Number2Transmission(number,delay,interval) -local numbers=UTILS.GetCharacters(number) -local wait=0 -for i=1,#numbers do -local n=numbers[i] -local transmission=UTILS.DeepCopy(self.numbers[n]) -transmission.Tplay=timer.getAbsTime()+(delay or 0) -if interval and i==1 then -transmission.interval=interval -end -self:AddTransmission(transmission) -wait=wait+transmission.duration -end -return wait -end -function RADIOQUEUE:Broadcast(transmission) -if((transmission.soundfile and transmission.soundfile.useSRS)or transmission.soundtext)and self.msrs then -self:_BroadcastSRS(transmission) -return -end -local sender=self:_GetRadioSender() -local filename=string.format("%s%s",transmission.path,transmission.filename) -if sender then -self:T(self.lid..string.format("Broadcasting from aircraft %s",sender:GetName())) -if not self.senderinit then -local commandFrequency={ -id="SetFrequency", -params={ -frequency=self.frequency, -modulation=self.modulation, -}} -sender:SetCommand(commandFrequency) -self.senderinit=true -end -local subtitle=nil -local duration=nil -if transmission.subtitle and transmission.subduration and transmission.subduration>0 then -subtitle=transmission.subtitle -duration=transmission.subduration -end -local commandTransmit={ -id="TransmitMessage", -params={ -file=filename, -duration=duration, -subtitle=subtitle, -loop=false, -}} -sender:SetCommand(commandTransmit) -if self.Debugmode then -local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s",filename,self.frequency/1000000,transmission.duration,transmission.subtitle or"") -MESSAGE:New(text,2,"RADIOQUEUE "..self.alias):ToAll() -end -else -self:T(self.lid..string.format("Broadcasting via trigger.action.radioTransmission().")) -local vec3=nil -if self.sendername then -vec3=self:_GetRadioSenderCoord() -end -if self.sendercoord and not vec3 then -vec3=self.sendercoord:GetVec3() -end -if vec3 then -self:T("Sending") -self:T({filename=filename,vec3=vec3,modulation=self.modulation,frequency=self.frequency,power=self.power}) -trigger.action.radioTransmission(filename,vec3,self.modulation,false,self.frequency,self.power) -if self.Debugmode then -local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s",filename,self.frequency/1000000,transmission.duration,transmission.subtitle or"") -MESSAGE:New(string.format(text,filename,transmission.duration,transmission.subtitle or""),5,"RADIOQUEUE "..self.alias):ToAll() -end -end -end -end -function RADIOQUEUE:_BroadcastSRS(transmission) -if transmission.soundfile and transmission.soundfile.useSRS then -self.msrs:PlaySoundFile(transmission.soundfile) -elseif transmission.soundtext then -self.msrs:PlaySoundText(transmission.soundtext) -end -end -function RADIOQUEUE:_CheckRadioQueueDelayed(delay) -self.checking=true -self:ScheduleOnce(delay or self.dt,RADIOQUEUE._CheckRadioQueue,self) -end -function RADIOQUEUE:_CheckRadioQueue() -if#self.queue==0 then -self.checking=false -return -end -local time=timer.getAbsTime() -local playing=false -local next=nil -local remove=nil -for i,_transmission in ipairs(self.queue)do -local transmission=_transmission -if time>=transmission.Tplay then -if transmission.isplaying then -if time>=transmission.Tstarted+transmission.duration then -transmission.isplaying=false -remove=i -self.Tlast=time -else -playing=true -end -else -local Tlast=self.Tlast -if transmission.interval==nil then -if next==nil then -next=transmission -end -else -if Tlast==nil or time-Tlast>=transmission.interval then -next=transmission -else -end -end -if next or Tlast then -break -end -end -else -end -end -if next~=nil and not playing then -self:Broadcast(next) -next.isplaying=true -next.Tstarted=time -end -if remove then -table.remove(self.queue,remove) -end -if self.schedonce then -self:_CheckRadioQueueDelayed() -end -end -function RADIOQUEUE:_GetRadioSender() -local sender=nil -if self.sendername then -sender=UNIT:FindByName(self.sendername) -if sender and sender:IsAlive()and(sender:IsAir()or sender:IsGround())then -return sender -end -end -return nil -end -function RADIOQUEUE:_GetRadioSenderCoord() -local vec3=nil -if self.sendername then -local sender=UNIT:FindByName(self.sendername) -if sender and sender:IsAlive()then -return sender:GetVec3() -end -local sender=STATIC:FindByName(self.sendername,false) -if sender then -return sender:GetVec3() -end -end -return nil -end -RADIOSPEECH={ -ClassName="RADIOSPEECH", -Vocabulary={ -EN={}, -DE={}, -RU={}, -} -} -RADIOSPEECH.Vocabulary.EN={ -["1"]={"1",0.25}, -["2"]={"2",0.25}, -["3"]={"3",0.30}, -["4"]={"4",0.35}, -["5"]={"5",0.35}, -["6"]={"6",0.42}, -["7"]={"7",0.38}, -["8"]={"8",0.20}, -["9"]={"9",0.32}, -["10"]={"10",0.35}, -["11"]={"11",0.40}, -["12"]={"12",0.42}, -["13"]={"13",0.38}, -["14"]={"14",0.42}, -["15"]={"15",0.42}, -["16"]={"16",0.52}, -["17"]={"17",0.59}, -["18"]={"18",0.40}, -["19"]={"19",0.47}, -["20"]={"20",0.38}, -["30"]={"30",0.29}, -["40"]={"40",0.35}, -["50"]={"50",0.32}, -["60"]={"60",0.44}, -["70"]={"70",0.48}, -["80"]={"80",0.26}, -["90"]={"90",0.36}, -["100"]={"100",0.55}, -["200"]={"200",0.55}, -["300"]={"300",0.61}, -["400"]={"400",0.60}, -["500"]={"500",0.61}, -["600"]={"600",0.65}, -["700"]={"700",0.70}, -["800"]={"800",0.54}, -["900"]={"900",0.60}, -["1000"]={"1000",0.60}, -["2000"]={"2000",0.61}, -["3000"]={"3000",0.64}, -["4000"]={"4000",0.62}, -["5000"]={"5000",0.69}, -["6000"]={"6000",0.69}, -["7000"]={"7000",0.75}, -["8000"]={"8000",0.59}, -["9000"]={"9000",0.65}, -["chevy"]={"chevy",0.35}, -["colt"]={"colt",0.35}, -["springfield"]={"springfield",0.65}, -["dodge"]={"dodge",0.35}, -["enfield"]={"enfield",0.5}, -["ford"]={"ford",0.32}, -["pontiac"]={"pontiac",0.55}, -["uzi"]={"uzi",0.28}, -["degrees"]={"degrees",0.5}, -["kilometers"]={"kilometers",0.65}, -["km"]={"kilometers",0.65}, -["miles"]={"miles",0.45}, -["meters"]={"meters",0.41}, -["mi"]={"miles",0.45}, -["feet"]={"feet",0.29}, -["br"]={"br",1.1}, -["bra"]={"bra",0.3}, -["returning to base"]={"returning_to_base",0.85}, -["on route to ground target"]={"on_route_to_ground_target",1.05}, -["intercepting bogeys"]={"intercepting_bogeys",1.00}, -["engaging ground target"]={"engaging_ground_target",1.20}, -["engaging bogeys"]={"engaging_bogeys",0.81}, -["wheels up"]={"wheels_up",0.42}, -["landing at base"]={"landing at base",0.8}, -["patrolling"]={"patrolling",0.55}, -["for"]={"for",0.31}, -["and"]={"and",0.31}, -["at"]={"at",0.3}, -["dot"]={"dot",0.26}, -["defender"]={"defender",0.45}, -} -RADIOSPEECH.Vocabulary.RU={ -["1"]={"1",0.34}, -["2"]={"2",0.30}, -["3"]={"3",0.23}, -["4"]={"4",0.51}, -["5"]={"5",0.31}, -["6"]={"6",0.44}, -["7"]={"7",0.25}, -["8"]={"8",0.43}, -["9"]={"9",0.45}, -["10"]={"10",0.53}, -["11"]={"11",0.66}, -["12"]={"12",0.70}, -["13"]={"13",0.66}, -["14"]={"14",0.80}, -["15"]={"15",0.65}, -["16"]={"16",0.75}, -["17"]={"17",0.74}, -["18"]={"18",0.85}, -["19"]={"19",0.80}, -["20"]={"20",0.58}, -["30"]={"30",0.51}, -["40"]={"40",0.51}, -["50"]={"50",0.67}, -["60"]={"60",0.76}, -["70"]={"70",0.68}, -["80"]={"80",0.84}, -["90"]={"90",0.71}, -["100"]={"100",0.35}, -["200"]={"200",0.59}, -["300"]={"300",0.53}, -["400"]={"400",0.70}, -["500"]={"500",0.50}, -["600"]={"600",0.58}, -["700"]={"700",0.64}, -["800"]={"800",0.77}, -["900"]={"900",0.75}, -["1000"]={"1000",0.87}, -["2000"]={"2000",0.83}, -["3000"]={"3000",0.84}, -["4000"]={"4000",1.00}, -["5000"]={"5000",0.77}, -["6000"]={"6000",0.90}, -["7000"]={"7000",0.77}, -["8000"]={"8000",0.92}, -["9000"]={"9000",0.87}, -["градусы"]={"degrees",0.5}, -["километры"]={"kilometers",0.65}, -["km"]={"kilometers",0.65}, -["мили"]={"miles",0.45}, -["mi"]={"miles",0.45}, -["метров"]={"meters",0.41}, -["m"]={"meters",0.41}, -["ноги"]={"feet",0.37}, -["br"]={"br",1.1}, -["bra"]={"bra",0.3}, -["возвращение на базу"]={"returning_to_base",1.40}, -["на пути к наземной цели"]={"on_route_to_ground_target",1.45}, -["перехват боги"]={"intercepting_bogeys",1.22}, -["поражение наземной цели"]={"engaging_ground_target",1.53}, -["привлечение болотных птиц"]={"engaging_bogeys",1.68}, -["колёса вверх..."]={"wheels_up",0.92}, -["посадка на базу"]={"landing at base",1.04}, -["патрулирование"]={"patrolling",0.96}, -["для"]={"for",0.27}, -["и"]={"and",0.17}, -["на сайте"]={"at",0.19}, -["точка"]={"dot",0.51}, -["защитник"]={"defender",0.45}, -} -function RADIOSPEECH:New(frequency,modulation) -local self=BASE:Inherit(self,RADIOQUEUE:New(frequency,modulation)) -self.Language="EN" -self:BuildTree() -return self -end -function RADIOSPEECH:SetLanguage(Langauge) -self.Language=Langauge -end -function RADIOSPEECH:AddSentenceToSpeech(RemainingSentence,Speech,Sentence,Data) -self:I({RemainingSentence,Speech,Sentence,Data}) -local Token,RemainingSentence=RemainingSentence:match("^ *([^ ]+)(.*)") -self:I({Token=Token,RemainingSentence=RemainingSentence}) -if Token then -if not Speech[Token]then -Speech[Token]={} -if RemainingSentence and RemainingSentence~=""then -Speech[Token].Next={} -self:AddSentenceToSpeech(RemainingSentence,Speech[Token].Next,Sentence,Data) -else -Speech[Token].Sentence=Sentence -Speech[Token].Data=Data -end -end -end -end -function RADIOSPEECH:BuildTree() -self.Speech={} -for Language,Sentences in pairs(self.Vocabulary)do -self:I({Language=Language,Sentences=Sentences}) -self.Speech[Language]={} -for Sentence,Data in pairs(Sentences)do -self:I({Sentence=Sentence,Data=Data}) -self:AddSentenceToSpeech(Sentence,self.Speech[Language],Sentence,Data) -end -end -self:I({Speech=self.Speech}) -return self -end -function RADIOSPEECH:SpeakWords(Sentence,Speech,Language) -local OriginalSentence=Sentence -local Word,RemainderSentence=Sentence:match("^[., ]*([^ .,]+)(.*)") -self:I({Word=Word,Speech=Speech[Word],RemainderSentence=RemainderSentence}) -if Word then -if Word~=""and tonumber(Word)==nil then -Word=Word:lower() -if Speech[Word]then -if Speech[Word].Next==nil then -self:I({Sentence=Speech[Word].Sentence,Data=Speech[Word].Data}) -self:NewTransmission(Speech[Word].Data[1]..".wav",Speech[Word].Data[2],Language.."/") -else -if RemainderSentence and RemainderSentence~=""then -return self:SpeakWords(RemainderSentence,Speech[Word].Next,Language) -end -end -end -return RemainderSentence -end -return OriginalSentence -else -return"" -end -end -function RADIOSPEECH:SpeakDigits(Sentence,Speech,Langauge) -local OriginalSentence=Sentence -local Digits,RemainderSentence=Sentence:match("^[., ]*([^ .,]+)(.*)") -self:I({Digits=Digits,Speech=Speech[Digits],RemainderSentence=RemainderSentence}) -if Digits then -if Digits~=""and tonumber(Digits)~=nil then -local Number=tonumber(Digits) -local Multiple=nil -while Number>=0 do -if Number>1000 then -Multiple=math.floor(Number/1000)*1000 -elseif Number>100 then -Multiple=math.floor(Number/100)*100 -elseif Number>20 then -Multiple=math.floor(Number/10)*10 -elseif Number>=0 then -Multiple=Number -end -Sentence=tostring(Multiple) -if Speech[Sentence]then -self:I({Speech=Speech[Sentence].Sentence,Data=Speech[Sentence].Data}) -self:NewTransmission(Speech[Sentence].Data[1]..".wav",Speech[Sentence].Data[2],Langauge.."/") -end -Number=Number-Multiple -Number=(Number==0)and-1 or Number -end -return RemainderSentence -end -return OriginalSentence -else -return"" -end -end -function RADIOSPEECH:Speak(Sentence,Language) -self:I({Sentence,Language}) -local Language=Language or"EN" -self:I({Language=Language}) -local Speech=self.Speech[Language] -self:I({Speech=Speech,Language=Language}) -self:NewTransmission("_In.wav",0.52,Language.."/") -repeat -Sentence=self:SpeakWords(Sentence,Speech,Language) -self:I({Sentence=Sentence}) -Sentence=self:SpeakDigits(Sentence,Speech,Language) -self:I({Sentence=Sentence}) -until not Sentence or Sentence=="" -self:NewTransmission("_Out.wav",0.28,Language.."/") -end -MSRS={ -ClassName="MSRS", -lid=nil, -port=5002, -name="MSRS", -backend="srsexe", -frequencies={}, -modulations={}, -coalition=0, -gender="female", -culture=nil, -voice=nil, -volume=1, -speed=1, -coordinate=nil, -provider="win", -Label="ROBOT", -ConfigFileName="Moose_MSRS.lua", -ConfigFilePath="Config\\", -ConfigLoaded=false, -poptions={}, -} -MSRS.version="0.3.0" -MSRS.Voices={ -Microsoft={ -["Hedda"]="Microsoft Hedda Desktop", -["Hazel"]="Microsoft Hazel Desktop", -["David"]="Microsoft David Desktop", -["Zira"]="Microsoft Zira Desktop", -["Hortense"]="Microsoft Hortense Desktop", -["de-DE-Hedda"]="Microsoft Hedda Desktop", -["en-GB-Hazel"]="Microsoft Hazel Desktop", -["en-US-David"]="Microsoft David Desktop", -["en-US-Zira"]="Microsoft Zira Desktop", -["fr-FR-Hortense"]="Microsoft Hortense Desktop", -}, -MicrosoftGRPC={ -["Hazel"]="Hazel", -["George"]="George", -["Susan"]="Susan", -["David"]="David", -["Zira"]="Zira", -["Mark"]="Mark", -["James"]="James", -["Catherine"]="Catherine", -["Richard"]="Richard", -["Linda"]="Linda", -["Ravi"]="Ravi", -["Heera"]="Heera", -["Sean"]="Sean", -["en_GB_Hazel"]="Hazel", -["en_GB_George"]="George", -["en_GB_Susan"]="Susan", -["en_US_David"]="David", -["en_US_Zira"]="Zira", -["en_US_Mark"]="Mark", -["en_AU_James"]="James", -["en_AU_Catherine"]="Catherine", -["en_CA_Richard"]="Richard", -["en_CA_Linda"]="Linda", -["en_IN_Ravi"]="Ravi", -["en_IN_Heera"]="Heera", -["en_IR_Sean"]="Sean", -}, -Google={ -Standard={ -["en_AU_Standard_A"]='en-AU-Standard-A', -["en_AU_Standard_B"]='en-AU-Standard-B', -["en_AU_Standard_C"]='en-AU-Standard-C', -["en_AU_Standard_D"]='en-AU-Standard-D', -["en_IN_Standard_A"]='en-IN-Standard-A', -["en_IN_Standard_B"]='en-IN-Standard-B', -["en_IN_Standard_C"]='en-IN-Standard-C', -["en_IN_Standard_D"]='en-IN-Standard-D', -["en_GB_Standard_A"]='en-GB-Standard-A', -["en_GB_Standard_B"]='en-GB-Standard-B', -["en_GB_Standard_C"]='en-GB-Standard-C', -["en_GB_Standard_D"]='en-GB-Standard-D', -["en_GB_Standard_F"]='en-GB-Standard-F', -["en_US_Standard_A"]='en-US-Standard-A', -["en_US_Standard_B"]='en-US-Standard-B', -["en_US_Standard_C"]='en-US-Standard-C', -["en_US_Standard_D"]='en-US-Standard-D', -["en_US_Standard_E"]='en-US-Standard-E', -["en_US_Standard_F"]='en-US-Standard-F', -["en_US_Standard_G"]='en-US-Standard-G', -["en_US_Standard_H"]='en-US-Standard-H', -["en_US_Standard_I"]='en-US-Standard-I', -["en_US_Standard_J"]='en-US-Standard-J', -["fr_FR_Standard_A"]="fr-FR-Standard-A", -["fr_FR_Standard_B"]="fr-FR-Standard-B", -["fr_FR_Standard_C"]="fr-FR-Standard-C", -["fr_FR_Standard_D"]="fr-FR-Standard-D", -["fr_FR_Standard_E"]="fr-FR-Standard-E", -["de_DE_Standard_A"]="de-DE-Standard-A", -["de_DE_Standard_B"]="de-DE-Standard-B", -["de_DE_Standard_C"]="de-DE-Standard-C", -["de_DE_Standard_D"]="de-DE-Standard-D", -["de_DE_Standard_E"]="de-DE-Standard-E", -["de_DE_Standard_F"]="de-DE-Standard-F", -["es_ES_Standard_A"]="es-ES-Standard-A", -["es_ES_Standard_B"]="es-ES-Standard-B", -["es_ES_Standard_C"]="es-ES-Standard-C", -["es_ES_Standard_D"]="es-ES-Standard-D", -["it_IT_Standard_A"]="it-IT-Standard-A", -["it_IT_Standard_B"]="it-IT-Standard-B", -["it_IT_Standard_C"]="it-IT-Standard-C", -["it_IT_Standard_D"]="it-IT-Standard-D", -}, -Wavenet={ -["en_AU_Wavenet_A"]='en-AU-Wavenet-A', -["en_AU_Wavenet_B"]='en-AU-Wavenet-B', -["en_AU_Wavenet_C"]='en-AU-Wavenet-C', -["en_AU_Wavenet_D"]='en-AU-Wavenet-D', -["en_IN_Wavenet_A"]='en-IN-Wavenet-A', -["en_IN_Wavenet_B"]='en-IN-Wavenet-B', -["en_IN_Wavenet_C"]='en-IN-Wavenet-C', -["en_IN_Wavenet_D"]='en-IN-Wavenet-D', -["en_GB_Wavenet_A"]='en-GB-Wavenet-A', -["en_GB_Wavenet_B"]='en-GB-Wavenet-B', -["en_GB_Wavenet_C"]='en-GB-Wavenet-C', -["en_GB_Wavenet_D"]='en-GB-Wavenet-D', -["en_GB_Wavenet_F"]='en-GB-Wavenet-F', -["en_US_Wavenet_A"]='en-US-Wavenet-A', -["en_US_Wavenet_B"]='en-US-Wavenet-B', -["en_US_Wavenet_C"]='en-US-Wavenet-C', -["en_US_Wavenet_D"]='en-US-Wavenet-D', -["en_US_Wavenet_E"]='en-US-Wavenet-E', -["en_US_Wavenet_F"]='en-US-Wavenet-F', -["en_US_Wavenet_G"]='en-US-Wavenet-G', -["en_US_Wavenet_H"]='en-US-Wavenet-H', -["en_US_Wavenet_I"]='en-US-Wavenet-I', -["en_US_Wavenet_J"]='en-US-Wavenet-J', -["fr_FR_Wavenet_A"]="fr-FR-Wavenet-A", -["fr_FR_Wavenet_B"]="fr-FR-Wavenet-B", -["fr_FR_Wavenet_C"]="fr-FR-Wavenet-C", -["fr_FR_Wavenet_D"]="fr-FR-Wavenet-D", -["fr_FR_Wavenet_E"]="fr-FR-Wavenet-E", -["de_DE_Wavenet_A"]="de-DE-Wavenet-A", -["de_DE_Wavenet_B"]="de-DE-Wavenet-B", -["de_DE_Wavenet_C"]="de-DE-Wavenet-C", -["de_DE_Wavenet_D"]="de-DE-Wavenet-D", -["de_DE_Wavenet_E"]="de-DE-Wavenet-E", -["de_DE_Wavenet_F"]="de-DE-Wavenet-F", -["es_ES_Wavenet_B"]="es-ES-Wavenet-B", -["es_ES_Wavenet_C"]="es-ES-Wavenet-C", -["es_ES_Wavenet_D"]="es-ES-Wavenet-D", -["it_IT_Wavenet_A"]="it-IT-Wavenet-A", -["it_IT_Wavenet_B"]="it-IT-Wavenet-B", -["it_IT_Wavenet_C"]="it-IT-Wavenet-C", -["it_IT_Wavenet_D"]="it-IT-Wavenet-D", -}, -}, -} -MSRS.Backend={ -SRSEXE="srsexe", -GRPC="grpc", -} -MSRS.Provider={ -WINDOWS="win", -GOOGLE="gcloud", -AZURE="azure", -AMAZON="aws", -} -function MSRS.uuid() -local random=math.random -local template='yxxx-xxxxxxxxxxxx' -return string.gsub(template,'[xy]',function(c) -local v=(c=='x')and random(0,0xf)or random(8,0xb) -return string.format('%x',v) -end) -end -function MSRS:New(Path,Frequency,Modulation,Backend) -local self=BASE:Inherit(self,BASE:New()) -self:F({Path,Frequency,Modulation,Backend}) -Frequency=Frequency or 143 -Modulation=Modulation or radio.modulation.AM -self.lid=string.format("%s-%s | ","unknown",self.version) -if not self.ConfigLoaded then -self:SetPath(Path) -self:SetPort() -self:SetFrequencies(Frequency) -self:SetModulations(Modulation) -self:SetGender() -self:SetCoalition() -self:SetLabel() -self:SetVolume() -self:SetBackend(Backend) -else -if Path then -self:SetPath(Path) -end -if Frequency then -self:SetFrequencies(Frequency) -end -if Modulation then -self:SetModulations(Modulation) -end -if Backend then -self:SetBackend(Backend) -end -end -self.lid=string.format("%s-%s | ",self.name,self.version) -if not io or not os then -self:E(self.lid.."***** ERROR - io or os NOT desanitized! MSRS will not work!") -end -return self -end -function MSRS:SetBackend(Backend) -self:F({Backend=Backend}) -Backend=Backend or MSRS.Backend.SRSEXE -local function Checker(back) -local ok=false -for _,_backend in pairs(MSRS.Backend)do -if tostring(back)==_backend then ok=true end -end -return ok -end -if Checker(Backend)then -self.backend=Backend -else -MESSAGE:New("ERROR: Backend "..tostring(Backend).." is not supported!",30,"MSRS",true):ToLog():ToAll() -end -return self -end -function MSRS:SetBackendGRPC() -self:F() -self:SetBackend(MSRS.Backend.GRPC) -return self -end -function MSRS:SetBackendSRSEXE() -self:F() -self:SetBackend(MSRS.Backend.SRSEXE) -return self -end -function MSRS.SetDefaultBackend(Backend) -MSRS.backend=Backend or MSRS.Backend.SRSEXE -end -function MSRS.SetDefaultBackendGRPC() -MSRS.backend=MSRS.Backend.GRPC -end -function MSRS:GetBackend() -return self.backend -end -function MSRS:SetPath(Path) -self:F({Path=Path}) -self.path=Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone" -local n=1;local nmax=1000 -while(self.path:sub(-1)=="/"or self.path:sub(-1)==[[\]])and n<=nmax do -self.path=self.path:sub(1,#self.path-1) -n=n+1 -end -self:F(string.format("SRS path=%s",self:GetPath())) -return self -end -function MSRS:GetPath() -return self.path -end -function MSRS:SetVolume(Volume) -self:F({Volume=Volume}) -local volume=Volume or 1 -if volume>1 then volume=1 elseif volume<0 then volume=0 end -self.volume=volume -return self -end -function MSRS:GetVolume() -return self.volume -end -function MSRS:SetLabel(Label) -self:F({Label=Label}) -self.Label=Label or"ROBOT" -return self -end -function MSRS:GetLabel() -return self.Label -end -function MSRS:SetPort(Port) -self:F({Port=Port}) -self.port=Port or 5002 -self:T(string.format("SRS port=%s",self:GetPort())) -return self -end -function MSRS:GetPort() -return self.port -end -function MSRS:SetCoalition(Coalition) -self:F({Coalition=Coalition}) -self.coalition=Coalition or 0 -return self -end -function MSRS:GetCoalition() -return self.coalition -end -function MSRS:SetFrequencies(Frequencies) -self:F(Frequencies) -self.frequencies=UTILS.EnsureTable(Frequencies,false) -return self -end -function MSRS:AddFrequencies(Frequencies) -self:F(Frequencies) -for _,_freq in pairs(UTILS.EnsureTable(Frequencies,false))do -self:T(self.lid..string.format("Adding frequency %s",tostring(_freq))) -table.insert(self.frequencies,_freq) -end -return self -end -function MSRS:GetFrequencies() -return self.frequencies -end -function MSRS:SetModulations(Modulations) -self:F(Modulations) -self.modulations=UTILS.EnsureTable(Modulations,false) -self:T(self.lid.."Modulations:") -self:T(self.modulations) -return self -end -function MSRS:AddModulations(Modulations) -self:F(Modulations) -for _,_mod in pairs(UTILS.EnsureTable(Modulations,false))do -table.insert(self.modulations,_mod) -end -return self -end -function MSRS:GetModulations() -return self.modulations -end -function MSRS:SetGender(Gender) -self:F({Gender=Gender}) -Gender=Gender or"female" -self.gender=Gender:lower() -self:T("Setting gender to "..tostring(self.gender)) -return self -end -function MSRS:SetCulture(Culture) -self:F({Culture=Culture}) -self.culture=Culture -return self -end -function MSRS:SetVoice(Voice) -self:F({Voice=Voice}) -self.voice=Voice -return self -end -function MSRS:SetVoiceProvider(Voice,Provider) -self:F({Voice=Voice,Provider=Provider}) -self.poptions=self.poptions or{} -self.poptions[Provider or self:GetProvider()]=Voice -return self -end -function MSRS:SetVoiceWindows(Voice) -self:F({Voice=Voice}) -self:SetVoiceProvider(Voice or"Microsoft Hazel Desktop",MSRS.Provider.WINDOWS) -return self -end -function MSRS:SetVoiceGoogle(Voice) -self:F({Voice=Voice}) -self:SetVoiceProvider(Voice or MSRS.Voices.Google.Standard.en_GB_Standard_A,MSRS.Provider.GOOGLE) -return self -end -function MSRS:SetVoiceAzure(Voice) -self:F({Voice=Voice}) -self:SetVoiceProvider(Voice or"en-US-AriaNeural",MSRS.Provider.AZURE) -return self -end -function MSRS:SetVoiceAmazon(Voice) -self:F({Voice=Voice}) -self:SetVoiceProvider(Voice or"Brian",MSRS.Provider.AMAZON) -return self -end -function MSRS:GetVoice(Provider) -Provider=Provider or self.provider -if Provider and self.poptions[Provider]and self.poptions[Provider].voice then -return self.poptions[Provider].voice -else -return self.voice -end -end -function MSRS:SetCoordinate(Coordinate) -self:F(Coordinate) -self.coordinate=Coordinate -return self -end -function MSRS:SetGoogle(PathToCredentials) -self:F({PathToCredentials=PathToCredentials}) -if PathToCredentials then -self.provider=MSRS.Provider.GOOGLE -self:SetProviderOptionsGoogle(PathToCredentials,PathToCredentials) -end -return self -end -function MSRS:SetGoogleAPIKey(APIKey) -self:F({APIKey=APIKey}) -if APIKey then -self.provider=MSRS.Provider.GOOGLE -if self.poptions[MSRS.Provider.GOOGLE]then -self.poptions[MSRS.Provider.GOOGLE].key=APIKey -else -self:SetProviderOptionsGoogle(nil,APIKey) -end -end -return self -end -function MSRS:SetProvider(Provider) -BASE:F({Provider=Provider}) -if self then -self.provider=Provider or MSRS.Provider.WINDOWS -return self -else -MSRS.provider=Provider or MSRS.Provider.WINDOWS -end -return -end -function MSRS:GetProvider() -return self.provider or MSRS.Provider.WINDOWS -end -function MSRS:SetProviderOptions(Provider,CredentialsFile,AccessKey,SecretKey,Region) -BASE:F({Provider,CredentialsFile,AccessKey,SecretKey,Region}) -local option=MSRS._CreateProviderOptions(Provider,CredentialsFile,AccessKey,SecretKey,Region) -if self then -self.poptions=self.poptions or{} -self.poptions[Provider]=option -else -MSRS.poptions=MSRS.poptions or{} -MSRS.poptions[Provider]=option -end -return option -end -function MSRS._CreateProviderOptions(Provider,CredentialsFile,AccessKey,SecretKey,Region) -BASE:F({Provider,CredentialsFile,AccessKey,SecretKey,Region}) -local option={} -option.provider=Provider -option.credentials=CredentialsFile -option.key=AccessKey -option.secret=SecretKey -option.region=Region -return option -end -function MSRS:SetProviderOptionsGoogle(CredentialsFile,AccessKey) -self:F({CredentialsFile,AccessKey}) -self:SetProviderOptions(MSRS.Provider.GOOGLE,CredentialsFile,AccessKey) -return self -end -function MSRS:SetProviderOptionsAmazon(AccessKey,SecretKey,Region) -self:F({AccessKey,SecretKey,Region}) -self:SetProviderOptions(MSRS.Provider.AMAZON,nil,AccessKey,SecretKey,Region) -return self -end -function MSRS:SetProviderOptionsAzure(AccessKey,Region) -self:F({AccessKey,Region}) -self:SetProviderOptions(MSRS.Provider.AZURE,nil,AccessKey,nil,Region) -return self -end -function MSRS:GetProviderOptions(Provider) -return self.poptions[Provider or self.provider]or{} -end -function MSRS:SetTTSProviderGoogle() -self:F() -self:SetProvider(MSRS.Provider.GOOGLE) -return self -end -function MSRS:SetTTSProviderMicrosoft() -self:F() -self:SetProvider(MSRS.Provider.WINDOWS) -return self -end -function MSRS:SetTTSProviderAzure() -self:F() -self:SetProvider(MSRS.Provider.AZURE) -return self -end -function MSRS:SetTTSProviderAmazon() -self:F() -self:SetProvider(MSRS.Provider.AMAZON) -return self -end -function MSRS:Help() -self:F() -local path=self:GetPath() -local exe="DCS-SR-ExternalAudio.exe" -local filename=os.getenv('TMP').."\\MSRS-help-"..MSRS.uuid()..".txt" -local command=string.format("%s/%s --help > %s",path,exe,filename) -os.execute(command) -local f=assert(io.open(filename,"rb")) -local data=f:read("*all") -f:close() -env.info("SRS help output:") -env.info("======================================================================") -env.info(data) -env.info("======================================================================") -return self -end -function MSRS:PlaySoundFile(Soundfile,Delay) -self:F({Soundfile,Delay}) -local soundfile=Soundfile:GetName() -local exists=UTILS.FileExists(soundfile) -if not exists then -self:E("ERROR: MSRS sound file does not exist! File="..soundfile) -return self -end -if Delay and Delay>0 then -self:ScheduleOnce(Delay,MSRS.PlaySoundFile,self,Soundfile,0) -else -local command=self:_GetCommand() -command=command..' --file="'..tostring(soundfile)..'"' -self:_ExecCommand(command) -end -return self -end -function MSRS:PlaySoundText(SoundText,Delay) -self:F({SoundText,Delay}) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,MSRS.PlaySoundText,self,SoundText,0) -else -if self.backend==MSRS.Backend.GRPC then -self:_DCSgRPCtts(SoundText.text,nil,SoundText.gender,SoundText.culture,SoundText.voice,SoundText.volume,SoundText.label,SoundText.coordinate) -else -local command=self:_GetCommand(nil,nil,nil,SoundText.gender,SoundText.voice,SoundText.culture,SoundText.volume,SoundText.speed) -command=command..string.format(" --text=\"%s\"",tostring(SoundText.text)) -self:_ExecCommand(command) -end -end -return self -end -function MSRS:PlayText(Text,Delay,Coordinate) -self:F({Text,Delay,Coordinate}) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,MSRS.PlayText,self,Text,nil,Coordinate) -else -if self.backend==MSRS.Backend.GRPC then -self:T(self.lid.."Transmitting") -self:_DCSgRPCtts(Text,nil,nil,nil,nil,nil,nil,Coordinate) -else -self:PlayTextExt(Text,Delay,nil,nil,nil,nil,nil,nil,nil,Coordinate) -end -end -return self -end -function MSRS:PlayTextExt(Text,Delay,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate) -self:T({Text,Delay,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate}) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,MSRS.PlayTextExt,self,Text,0,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate) -else -Frequencies=Frequencies or self:GetFrequencies() -Modulations=Modulations or self:GetModulations() -if self.backend==MSRS.Backend.SRSEXE then -local command=self:_GetCommand(UTILS.EnsureTable(Frequencies,false),UTILS.EnsureTable(Modulations,false),nil,Gender,Voice,Culture,Volume,nil,nil,Label,Coordinate) -command=command..string.format(" --text=\"%s\"",tostring(Text)) -self:_ExecCommand(command) -elseif self.backend==MSRS.Backend.GRPC then -self:_DCSgRPCtts(Text,Frequencies,Gender,Culture,Voice,Volume,Label,Coordinate) -end -end -return self -end -function MSRS:PlayTextFile(TextFile,Delay) -self:F({TextFile,Delay}) -if Delay and Delay>0 then -self:ScheduleOnce(Delay,MSRS.PlayTextFile,self,TextFile,0) -else -local exists=UTILS.FileExists(TextFile) -if not exists then -self:E("ERROR: MSRS Text file does not exist! File="..tostring(TextFile)) -return self -end -local command=self:_GetCommand() -command=command..string.format(" --textFile=\"%s\"",tostring(TextFile)) -self:T(string.format("MSRS TextFile command=%s",command)) -local l=string.len(command) -self:T(string.format("Command length=%d",l)) -self:_ExecCommand(command) -end -return self -end -function MSRS:_GetLatLongAlt(Coordinate) -self:F({Coordinate=Coordinate}) -local lat=0.0 -local lon=0.0 -local alt=0.0 -if Coordinate then -lat,lon,alt=coord.LOtoLL(Coordinate) -end -return lat,lon,math.floor(alt) -end -function MSRS:_GetCommand(freqs,modus,coal,gender,voice,culture,volume,speed,port,label,coordinate) -self:F({freqs,modus,coal,gender,voice,culture,volume,speed,port,label,coordinate}) -local path=self:GetPath() -local exe="DCS-SR-ExternalAudio.exe" -local fullPath=string.format("%s\\%s",path,exe) -freqs=table.concat(freqs or self.frequencies,",") -modus=table.concat(modus or self.modulations,",") -coal=coal or self.coalition -gender=gender or self.gender -voice=voice or self:GetVoice(self.provider)or self.voice -culture=culture or self.culture -volume=volume or self.volume -speed=speed or self.speed -port=port or self.port -label=label or self.Label -coordinate=coordinate or self.coordinate -modus=modus:gsub("0","AM") -modus=modus:gsub("1","FM") -local command=string.format('"%s\\%s" -f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"',path,exe,freqs,modus,coal,port,label,volume) -if voice then -command=command..string.format(" --voice=\"%s\"",tostring(voice)) -else -if gender and gender~="female"then -command=command..string.format(" -g %s",tostring(gender)) -end -if culture and culture~="en-GB"then -command=command..string.format(" -l %s",tostring(culture)) -end -end -if coordinate then -local lat,lon,alt=self:_GetLatLongAlt(coordinate) -command=command..string.format(" -L %.4f -O %.4f -A %d",lat,lon,alt) -end -if self.provider==MSRS.Provider.GOOGLE then -local pops=self:GetProviderOptions() -command=command..string.format(' --ssml -G "%s"',pops.credentials) -elseif self.provider==MSRS.Provider.WINDOWS then -else -self:E("ERROR: SRS only supports WINWOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as ") -end -if not UTILS.FileExists(fullPath)then -self:E("ERROR: MSRS SRS executable does not exist! FullPath="..fullPath) -command="CommandNotFound" -end -self:T("MSRS command from _GetCommand="..command) -return command -end -function MSRS:_ExecCommand(command) -self:F({command=command}) -if string.find(command,"CommandNotFound")then return 0 end -local batContent=command.." && exit" -local filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".bat" -local script=io.open(filename,"w+") -script:write(batContent) -script:close() -self:T("MSRS batch file created: "..filename) -self:T("MSRS batch content: "..batContent) -local res=nil -if true then -local filenvbs=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".vbs" -local script=io.open(filenvbs,"w+") -script:write(string.format('Dim WinScriptHost\n')) -script:write(string.format('Set WinScriptHost = CreateObject("WScript.Shell")\n')) -script:write(string.format('WinScriptHost.Run Chr(34) & "%s" & Chr(34), 0\n',filename)) -script:write(string.format('Set WinScriptHost = Nothing')) -script:close() -self:T("MSRS vbs file created to start batch="..filenvbs) -local runvbs=string.format('cscript.exe //Nologo //B "%s"',filenvbs) -self:T("MSRS execute VBS command="..runvbs) -res=os.execute(runvbs) -timer.scheduleFunction(os.remove,filename,timer.getTime()+1) -timer.scheduleFunction(os.remove,filenvbs,timer.getTime()+1) -self:T("MSRS vbs and batch file removed") -elseif false then -local filenvbs=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".vbs" -local script=io.open(filenvbs,"w+") -script:write(string.format('Set oShell = CreateObject ("Wscript.Shell")\n')) -script:write(string.format('Dim strArgs\n')) -script:write(string.format('strArgs = "cmd /c %s"\n',filename)) -script:write(string.format('oShell.Run strArgs, 0, false')) -script:close() -local runvbs=string.format('cscript.exe //Nologo //B "%s"',filenvbs) -res=os.execute(runvbs) -else -command=string.format('start /b "" "%s"',filename) -self:T("MSRS execute command="..command) -res=os.execute(command) -timer.scheduleFunction(os.remove,filename,timer.getTime()+1) -end -return res -end -function MSRS:_DCSgRPCtts(Text,Frequencies,Gender,Culture,Voice,Volume,Label,Coordinate) -self:F("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts()") -self:F({Text,Frequencies,Gender,Culture,Voice,Volume,Label,Coordinate}) -local options={} -local ssml=Text or'' -Frequencies=UTILS.EnsureTable(Frequencies,true)or self:GetFrequencies() -options.plaintext=Text -options.srsClientName=Label or self.Label -if self.coordinate then -options.position={} -options.position.lat,options.position.lon,options.position.alt=self:_GetLatLongAlt(self.coordinate) -end -options.coalition=UTILS.GetCoalitionName(self.coalition):lower() -local provider=self.provider or MSRS.Provider.WINDOWS -self:F({provider=provider}) -options.provider={} -options.provider[provider]=self:GetProviderOptions(provider) -Voice=Voice or self:GetVoice(self.provider)or self.voice -if Voice then -options.provider[provider].voice=Voice -else -local preTag,genderProp,langProp,postTag='','','','' -local gender="" -if self.gender then -gender=string.format(' gender=\"%s\"',self.gender) -end -local language="" -if self.culture then -language=string.format(' language=\"%s\"',self.culture) -end -if self.gender or self.culture then -ssml=string.format("%s",gender,language,Text) -end -end -for _,freq in pairs(Frequencies)do -self:F("Calling GRPC.tts with the following parameter:") -self:F({ssml=ssml,freq=freq,options=options}) -self:F(options.provider[provider]) -GRPC.tts(ssml,freq*1e6,options) -end -end -function MSRS:LoadConfigFile(Path,Filename) -if lfs==nil then -env.info("*****Note - lfs and os need to be desanitized for MSRS to work!") -return false -end -local path=Path or lfs.writedir()..MSRS.ConfigFilePath -local file=Filename or MSRS.ConfigFileName or"Moose_MSRS.lua" -local pathandfile=path..file -local filexsists=UTILS.FileExists(pathandfile) -if filexsists and not MSRS.ConfigLoaded then -env.info("FF reading config file") -assert(loadfile(path..file))() -if MSRS_Config then -local Self=self or MSRS -Self.path=MSRS_Config.Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone" -Self.port=MSRS_Config.Port or 5002 -Self.backend=MSRS_Config.Backend or MSRS.Backend.SRSEXE -Self.frequencies=MSRS_Config.Frequency or{127,243} -Self.modulations=MSRS_Config.Modulation or{0,0} -Self.coalition=MSRS_Config.Coalition or 0 -if MSRS_Config.Coordinate then -Self.coordinate=COORDINATE:New(MSRS_Config.Coordinate[1],MSRS_Config.Coordinate[2],MSRS_Config.Coordinate[3]) -end -Self.culture=MSRS_Config.Culture or"en-GB" -Self.gender=MSRS_Config.Gender or"male" -Self.Label=MSRS_Config.Label or"MSRS" -Self.voice=MSRS_Config.Voice -Self.provider=MSRS_Config.Provider or MSRS.Provider.WINDOWS -for _,provider in pairs(MSRS.Provider)do -if MSRS_Config[provider]then -Self.poptions[provider]=MSRS_Config[provider] -end -end -Self.ConfigLoaded=true -end -env.info("MSRS - Successfully loaded default configuration from disk!",false) -end -if not filexsists then -env.info("MSRS - Cannot find default configuration file!",false) -return false -end -return true -end -function MSRS.getSpeechTime(length,speed,isGoogle) -local maxRateRatio=3 -speed=speed or 1.0 -isGoogle=isGoogle or false -local speedFactor=1.0 -if isGoogle then -speedFactor=speed -else -if speed~=0 then -speedFactor=math.abs(speed)*(maxRateRatio-1)/10+1 -end -if speed<0 then -speedFactor=1/speedFactor -end -end -local wpm=math.ceil(100*speedFactor) -local cps=math.floor((wpm*5)/60) -if type(length)=="string"then -length=string.len(length) -end -return length/cps -end -MSRSQUEUE={ -ClassName="MSRSQUEUE", -Debugmode=nil, -lid=nil, -queue={}, -alias=nil, -dt=nil, -Tlast=nil, -checking=nil, -} -function MSRSQUEUE:New(alias) -local self=BASE:Inherit(self,BASE:New()) -self.alias=alias or"My Radio" -self.dt=1.0 -self.lid=string.format("MSRSQUEUE %s | ",self.alias) -return self -end -function MSRSQUEUE:Clear() -self:T(self.lid.."Clearing MSRSQUEUE") -self.queue={} -return self -end -function MSRSQUEUE:AddTransmission(transmission) -transmission.isplaying=false -transmission.Tstarted=nil -table.insert(self.queue,transmission) -if not self.checking then -self:_CheckRadioQueue() -end -return self -end -function MSRSQUEUE:SetTransmitOnlyWithPlayers(Switch) -self.TransmitOnlyWithPlayers=Switch -if Switch==false or Switch==nil then -if self.PlayerSet then -self.PlayerSet:FilterStop() -end -self.PlayerSet=nil -else -self.PlayerSet=SET_CLIENT:New():FilterStart() -end -return self -end -function MSRSQUEUE:NewTransmission(text,duration,msrs,tstart,interval,subgroups,subtitle,subduration,frequency,modulation,gender,culture,voice,volume,label,coordinate) -if self.TransmitOnlyWithPlayers then -if self.PlayerSet and self.PlayerSet:CountAlive()==0 then -return self -end -end -if not text then -self:E(self.lid.."ERROR: No text specified.") -return nil -end -if type(text)~="string"then -self:E(self.lid.."ERROR: Text specified is NOT a string.") -return nil -end -local transmission={} -transmission.text=text -transmission.duration=duration or MSRS.getSpeechTime(text) -transmission.msrs=msrs -transmission.Tplay=tstart or timer.getAbsTime() -transmission.subtitle=subtitle -transmission.interval=interval or 0 -transmission.frequency=frequency or msrs.frequencies -transmission.modulation=modulation or msrs.modulations -transmission.subgroups=subgroups -if transmission.subtitle then -transmission.subduration=subduration or transmission.duration -else -transmission.subduration=0 -end -transmission.gender=gender or msrs.gender -transmission.culture=culture or msrs.culture -transmission.voice=voice or msrs.voice -transmission.volume=volume or msrs.volume -transmission.label=label or msrs.Label -transmission.coordinate=coordinate or msrs.coordinate -self:AddTransmission(transmission) -return transmission -end -function MSRSQUEUE:Broadcast(transmission) -self:T(self.lid.."Broadcast") -if transmission.frequency then -transmission.msrs:PlayTextExt(transmission.text,nil,transmission.frequency,transmission.modulation,transmission.gender,transmission.culture,transmission.voice,transmission.volume,transmission.label,transmission.coordinate) -else -transmission.msrs:PlayText(transmission.text,nil,transmission.coordinate) -end -local function texttogroup(gid) -trigger.action.outTextForGroup(gid,transmission.subtitle,transmission.subduration,true) -end -if transmission.subgroups and#transmission.subgroups>0 then -for _,_group in pairs(transmission.subgroups)do -local group=_group -if group and group:IsAlive()then -local gid=group:GetID() -self:ScheduleOnce(4,texttogroup,gid) -end -end -end -end -function MSRSQUEUE:CalcTransmisstionDuration() -local Tnow=timer.getAbsTime() -local T=0 -for _,_transmission in pairs(self.queue)do -local transmission=_transmission -if transmission.isplaying then -local dt=Tnow-transmission.Tstarted -T=T+transmission.duration-dt -else -T=T+transmission.duration -end -end -return T -end -function MSRSQUEUE:_CheckRadioQueue(delay) -local N=#self.queue -self:T2(self.lid..string.format("Check radio queue %s: delay=%.3f sec, N=%d, checking=%s",self.alias,delay or 0,N,tostring(self.checking))) -if delay and delay>0 then -self:ScheduleOnce(delay,MSRSQUEUE._CheckRadioQueue,self) -self.checking=true -else -if N==0 then -self:T(self.lid..string.format("Check radio queue %s empty ==> disable checking",self.alias)) -self.checking=false -return -end -local time=timer.getAbsTime() -self.checking=true -local dt=self.dt -local playing=false -local next=nil -local remove=nil -for i,_transmission in ipairs(self.queue)do -local transmission=_transmission -if time>=transmission.Tplay then -if transmission.isplaying then -if time>=transmission.Tstarted+transmission.duration then -transmission.isplaying=false -remove=i -self.Tlast=time -else -playing=true -dt=transmission.duration-(time-transmission.Tstarted) -end -else -local Tlast=self.Tlast -if transmission.interval==nil then -if next==nil then -next=transmission -end -else -if Tlast==nil or time-Tlast>=transmission.interval then -next=transmission -else -end -end -if next or Tlast then -break -end -end -else -end -end -if next~=nil and not playing then -self:T(self.lid..string.format("Broadcasting text=\"%s\" at T=%.3f",next.text,time)) -self:Broadcast(next) -next.isplaying=true -next.Tstarted=time -dt=next.duration -end -if remove then -table.remove(self.queue,remove) -N=N-1 -if#self.queue==0 then -self:T(self.lid..string.format("Check radio queue %s empty ==> disable checking",self.alias)) -self.checking=false -return -end -end -self:_CheckRadioQueue(dt) -end -end -MSRS.LoadConfigFile() -COMMANDCENTER={ -ClassName="COMMANDCENTER", -CommandCenterName="", -CommandCenterCoalition=nil, -CommandCenterPositionable=nil, -Name="", -ReferencePoints={}, -ReferenceNames={}, -CommunicationMode="80", -} -COMMANDCENTER.AutoAssignMethods={ -["Random"]=1, -["Distance"]=2, -["Priority"]=3, -} -function COMMANDCENTER:New(CommandCenterPositionable,CommandCenterName) -local self=BASE:Inherit(self,BASE:New()) -self.CommandCenterPositionable=CommandCenterPositionable -self.CommandCenterName=CommandCenterName or CommandCenterPositionable:GetName() -self.CommandCenterCoalition=CommandCenterPositionable:GetCoalition() -self.Missions={} -self:SetAutoAssignTasks(false) -self:SetAutoAcceptTasks(true) -self:SetAutoAssignMethod(COMMANDCENTER.AutoAssignMethods.Distance) -self:SetFlashStatus(false) -self:SetMessageDuration(10) -self:HandleEvent(EVENTS.Birth, -function(self,EventData) -if EventData.IniObjectCategory==1 then -local EventGroup=GROUP:Find(EventData.IniDCSGroup) -if EventGroup and EventGroup:IsAlive()and self:HasGroup(EventGroup)then -local CommandCenterMenu=MENU_GROUP:New(EventGroup,self:GetText()) -local MenuReporting=MENU_GROUP:New(EventGroup,"Missions Reports",CommandCenterMenu) -local MenuMissionsSummary=MENU_GROUP_COMMAND:New(EventGroup,"Missions Status Report",MenuReporting,self.ReportSummary,self,EventGroup) -local MenuMissionsDetails=MENU_GROUP_COMMAND:New(EventGroup,"Missions Players Report",MenuReporting,self.ReportMissionsPlayers,self,EventGroup) -local PlayerUnit=EventData.IniUnit -for MissionID,Mission in pairs(self:GetMissions())do -local Mission=Mission -local PlayerGroup=EventData.IniGroup -Mission:JoinUnit(PlayerUnit,PlayerGroup) -end -self:SetMenu() -end -end -end -) -self:HandleEvent(EVENTS.MissionEnd, -function(self,EventData) -local PlayerUnit=EventData.IniUnit -for MissionID,Mission in pairs(self:GetMissions())do -local Mission=Mission -Mission:Stop() -end -end -) -self:HandleEvent(EVENTS.PlayerLeaveUnit, -function(self,EventData) -local PlayerUnit=EventData.IniUnit -for MissionID,Mission in pairs(self:GetMissions())do -local Mission=Mission -if Mission:IsENGAGED()then -Mission:AbortUnit(PlayerUnit) -end -end -end -) -self:HandleEvent(EVENTS.Crash, -function(self,EventData) -local PlayerUnit=EventData.IniUnit -for MissionID,Mission in pairs(self:GetMissions())do -local Mission=Mission -if Mission:IsENGAGED()then -Mission:CrashUnit(PlayerUnit) -end -end -end -) -self:SetMenu() -_SETTINGS:SetSystemMenu(CommandCenterPositionable) -self:SetCommandMenu() -return self -end -function COMMANDCENTER:GetName() -return self.CommandCenterName -end -function COMMANDCENTER:GetText() -return"Command Center ["..self.CommandCenterName.."]" -end -function COMMANDCENTER:GetShortText() -return"CC ["..self.CommandCenterName.."]" -end -function COMMANDCENTER:GetCoalition() -return self.CommandCenterCoalition -end -function COMMANDCENTER:GetPositionable() -return self.CommandCenterPositionable -end -function COMMANDCENTER:GetMissions() -return self.Missions or{} -end -function COMMANDCENTER:AddMission(Mission) -self.Missions[Mission]=Mission -return Mission -end -function COMMANDCENTER:RemoveMission(Mission) -self.Missions[Mission]=nil -return Mission -end -function COMMANDCENTER:SetReferenceZones(ReferenceZonePrefix) -local MatchPattern="(.*)#(.*)" -self:F({MatchPattern=MatchPattern}) -for ReferenceZoneName in pairs(_DATABASE.ZONENAMES)do -local ZoneName,ReferenceName=string.match(ReferenceZoneName,MatchPattern) -self:F({ZoneName=ZoneName,ReferenceName=ReferenceName}) -if ZoneName and ReferenceName and ZoneName==ReferenceZonePrefix then -self.ReferencePoints[ReferenceZoneName]=ZONE:New(ReferenceZoneName) -self.ReferenceNames[ReferenceZoneName]=ReferenceName -end -end -return self -end -function COMMANDCENTER:SetModeWWII() -self.CommunicationMode="WWII" -return self -end -function COMMANDCENTER:IsModeWWII() -return self.CommunicationMode=="WWII" -end -function COMMANDCENTER:SetMenu() -self:F2() -local MenuTime=timer.getTime() -for MissionID,Mission in pairs(self:GetMissions()or{})do -local Mission=Mission -Mission:SetMenu(MenuTime) -end -for MissionID,Mission in pairs(self:GetMissions()or{})do -Mission=Mission -Mission:RemoveMenu(MenuTime) -end -end -function COMMANDCENTER:GetMenu(TaskGroup) -local MenuTime=timer.getTime() -self.CommandCenterMenus=self.CommandCenterMenus or{} -local CommandCenterMenu -local CommandCenterText=self:GetText() -CommandCenterMenu=MENU_GROUP:New(TaskGroup,CommandCenterText):SetTime(MenuTime) -self.CommandCenterMenus[TaskGroup]=CommandCenterMenu -if self.AutoAssignTasks==false then -local AssignTaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,"Assign Task",CommandCenterMenu,self.AssignTask,self,TaskGroup):SetTime(MenuTime):SetTag("AutoTask") -end -CommandCenterMenu:Remove(MenuTime,"AutoTask") -return self.CommandCenterMenus[TaskGroup] -end -function COMMANDCENTER:AssignTask(TaskGroup) -local Tasks={} -local AssignPriority=99999999 -local AutoAssignMethod=self.AutoAssignMethod -for MissionID,Mission in pairs(self:GetMissions())do -local Mission=Mission -local MissionTasks=Mission:GetGroupTasks(TaskGroup) -for MissionTaskName,MissionTask in pairs(MissionTasks or{})do -local MissionTask=MissionTask -if MissionTask:IsStatePlanned()or MissionTask:IsStateReplanned()or MissionTask:IsStateAssigned()then -local TaskPriority=MissionTask:GetAutoAssignPriority(self.AutoAssignMethod,self,TaskGroup) -if TaskPriority Adding TASK ",MissionName=self:GetName(),TaskName=TaskName}) -self.Tasks[TaskName]=Task -self:GetCommandCenter():SetMenu() -return Task -end -function MISSION:RemoveTask(Task) -local TaskName=Task:GetTaskName() -self:T({"<== Removing TASK ",MissionName=self:GetName(),TaskName=TaskName}) -self:F(TaskName) -self.Tasks[TaskName]=self.Tasks[TaskName]or{n=0} -self.Tasks[TaskName]=nil -Task=nil -collectgarbage() -self:GetCommandCenter():SetMenu() -return nil -end -function MISSION:IsCOMPLETED() -return self:Is("COMPLETED") -end -function MISSION:IsIDLE() -return self:Is("IDLE") -end -function MISSION:IsENGAGED() -return self:Is("ENGAGED") -end -function MISSION:IsFAILED() -return self:Is("FAILED") -end -function MISSION:IsHOLD() -return self:Is("HOLD") -end -function MISSION:HasGroup(TaskGroup) -local Has=false -for TaskID,Task in pairs(self:GetTasks())do -local Task=Task -if Task:HasGroup(TaskGroup)then -Has=true -break -end -end -return Has -end -function MISSION:GetTasksRemaining() -local TasksRemaining=0 -for TaskID,Task in pairs(self:GetTasks())do -local Task=Task -if Task:IsStateSuccess()or Task:IsStateFailed()then -else -TasksRemaining=TasksRemaining+1 -end -end -return TasksRemaining -end -function MISSION:GetTaskTypes() -local TaskTypeList={} -local TasksRemaining=0 -for TaskID,Task in pairs(self:GetTasks())do -local Task=Task -local TaskType=Task:GetType() -TaskTypeList[TaskType]=TaskType -end -return TaskTypeList -end -function MISSION:AddPlayerName(PlayerName) -self.PlayerNames=self.PlayerNames or{} -self.PlayerNames[PlayerName]=PlayerName -return self -end -function MISSION:GetPlayerNames() -return self.PlayerNames -end -function MISSION:ReportBriefing() -local Report=REPORT:New() -local Name=self:GetText() -local Status="<"..self:GetState()..">" -Report:Add(string.format('%s - %s - Mission Briefing Report',Name,Status)) -Report:Add(self.MissionBriefing) -return Report:Text() -end -function MISSION:ReportPlayersPerTask(ReportGroup) -local Report=REPORT:New() -local Name=self:GetText() -local Status="<"..self:GetState()..">" -Report:Add(string.format('%s - %s - Players per Task Report',Name,Status)) -local PlayerList={} -for TaskID,Task in pairs(self:GetTasks())do -local Task=Task -local PlayerNames=Task:GetPlayerNames() -for PlayerName,PlayerGroup in pairs(PlayerNames)do -PlayerList[PlayerName]=Task:GetName() -end -end -for PlayerName,TaskName in pairs(PlayerList)do -Report:Add(string.format(' - Player (%s): Task "%s"',PlayerName,TaskName)) -end -return Report:Text() -end -function MISSION:ReportPlayersProgress(ReportGroup) -local Report=REPORT:New() -local Name=self:GetText() -local Status="<"..self:GetState()..">" -Report:Add(string.format('%s - %s - Players per Task Progress Report',Name,Status)) -local PlayerList={} -for TaskID,Task in pairs(self:GetTasks())do -local Task=Task -local TaskName=Task:GetName() -local Goal=Task:GetGoal() -PlayerList[TaskName]=PlayerList[TaskName]or{} -if Goal then -local TotalContributions=Goal:GetTotalContributions() -local PlayerContributions=Goal:GetPlayerContributions() -self:F({TotalContributions=TotalContributions,PlayerContributions=PlayerContributions}) -for PlayerName,PlayerContribution in pairs(PlayerContributions)do -PlayerList[TaskName][PlayerName]=string.format('Player (%s): Task "%s": %d%%',PlayerName,TaskName,PlayerContributions[PlayerName]*100/TotalContributions) -end -else -PlayerList[TaskName]["_"]=string.format('Player (---): Task "%s": %d%%',TaskName,0) -end -end -for TaskName,TaskData in pairs(PlayerList)do -for PlayerName,TaskText in pairs(TaskData)do -Report:Add(string.format(' - %s',TaskText)) -end -end -return Report:Text() -end -function MISSION:MarkTargetLocations(ReportGroup) -local Report=REPORT:New() -local Name=self:GetText() -local Status="<"..self:GetState()..">" -Report:Add(string.format('%s - %s - All Tasks are marked on the map. Select a Task from the Mission Menu and Join the Task!!!',Name,Status)) -for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)" -Report:Add(string.format('%s - %s - Task Overview Report',Name,Status)) -for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)" -Report:Add(string.format('%s - %s - %s Tasks Report',Name,Status,TaskStatus)) -local Tasks=0 -for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)=8 then -break -end -end -return Report:Text() -end -function MISSION:ReportDetails(ReportGroup) -local Report=REPORT:New() -local Name=self:GetText() -local Status="<"..self:GetState()..">" -Report:Add(string.format('%s - %s - Task Detailed Report',Name,Status)) -local TasksRemaining=0 -for TaskID,Task in pairs(self:GetTasks())do -local Task=Task -Report:Add(string.rep("-",140)) -Report:Add(Task:ReportDetails(ReportGroup)) -end -return Report:Text() -end -function MISSION:GetTasks() -return self.Tasks or{} -end -function MISSION:GetGroupTasks(TaskGroup) -local Tasks={} -for TaskID,Task in pairs(self:GetTasks())do -local Task=Task -if Task:HasGroup(TaskGroup)then -Tasks[#Tasks+1]=Task -end -end -return Tasks -end -function MISSION:MenuReportBriefing(ReportGroup) -local Report=self:ReportBriefing() -self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Briefing) -end -function MISSION:MenuMarkTargetLocations(ReportGroup) -local Report=self:MarkTargetLocations(ReportGroup) -self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) -end -function MISSION:MenuReportTasksSummary(ReportGroup) -local Report=self:ReportSummary(ReportGroup) -self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) -end -function MISSION:MenuReportTasksPerStatus(ReportGroup,TaskStatus) -local Report=self:ReportOverview(ReportGroup,TaskStatus) -self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) -end -function MISSION:MenuReportPlayersPerTask(ReportGroup) -local Report=self:ReportPlayersPerTask() -self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) -end -function MISSION:MenuReportPlayersProgress(ReportGroup) -local Report=self:ReportPlayersProgress() -self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) -end -TASK={ -ClassName="TASK", -TaskScheduler=nil, -ProcessClasses={}, -Processes={}, -Players=nil, -Scores={}, -Menu={}, -SetGroup=nil, -FsmTemplate=nil, -Mission=nil, -CommandCenter=nil, -TimeOut=0, -AssignedGroups={}, -} -function TASK:New(Mission,SetGroupAssign,TaskName,TaskType,TaskBriefing) -local self=BASE:Inherit(self,FSM_TASK:New(TaskName)) -self:SetStartState("Planned") -self:AddTransition("Planned","Assign","Assigned") -self:AddTransition("Assigned","AssignUnit","Assigned") -self:AddTransition("Assigned","Success","Success") -self:AddTransition("Assigned","Hold","Hold") -self:AddTransition("Assigned","Fail","Failed") -self:AddTransition({"Planned","Assigned"},"Abort","Aborted") -self:AddTransition("Assigned","Cancel","Cancelled") -self:AddTransition("Assigned","Goal","*") -self.Fsm={} -local Fsm=self:GetUnitProcess() -Fsm:SetStartState("Planned") -Fsm:AddProcess("Planned","Accept",ACT_ASSIGN_ACCEPT:New(self.TaskBriefing),{Assigned="Assigned",Rejected="Reject"}) -Fsm:AddTransition("Assigned","Assigned","*") -self:AddTransition("*","PlayerCrashed","*") -self:AddTransition("*","PlayerAborted","*") -self:AddTransition("*","PlayerRejected","*") -self:AddTransition("*","PlayerDead","*") -self:AddTransition({"Failed","Aborted","Cancelled"},"Replan","Planned") -self:AddTransition("*","TimeOut","Cancelled") -self:F("New TASK "..TaskName) -self.Processes={} -self.Mission=Mission -self.CommandCenter=Mission:GetCommandCenter() -self.SetGroup=SetGroupAssign -self:SetType(TaskType) -self:SetName(TaskName) -self:SetID(Mission:GetNextTaskID(self)) -self:SetBriefing(TaskBriefing) -self.TaskInfo=TASKINFO:New(self) -self.TaskProgress={} -return self -end -function TASK:GetUnitProcess(TaskUnit) -if TaskUnit then -return self:GetStateMachine(TaskUnit) -else -self.FsmTemplate=self.FsmTemplate or FSM_PROCESS:New() -return self.FsmTemplate -end -end -function TASK:SetUnitProcess(FsmTemplate) -self.FsmTemplate=FsmTemplate -end -function TASK:JoinUnit(PlayerUnit,PlayerGroup) -self:F({PlayerUnit=PlayerUnit,PlayerGroup=PlayerGroup}) -local PlayerUnitAdded=false -local PlayerGroups=self:GetGroups() -if PlayerGroups:IsIncludeObject(PlayerGroup)then -if self:IsStatePlanned()or self:IsStateReplanned()then -end -if self:IsStateAssigned()then -local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup) -self:F({IsGroupAssigned=IsGroupAssigned}) -if IsGroupAssigned then -self:AssignToUnit(PlayerUnit) -self:MessageToGroups(PlayerUnit:GetPlayerName().." joined Task "..self:GetName()) -end -end -end -return PlayerUnitAdded -end -function TASK:RejectGroup(PlayerGroup) -local PlayerGroups=self:GetGroups() -if PlayerGroups:IsIncludeObject(PlayerGroup)then -if self:IsStatePlanned()then -local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup) -if IsGroupAssigned then -local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName() -self:GetMission():GetCommandCenter():MessageToGroup("Task "..self:GetName().." has been rejected! We will select another task.",PlayerGroup) -self:UnAssignFromGroup(PlayerGroup) -self:PlayerRejected(PlayerGroup:GetUnit(1)) -end -end -end -return self -end -function TASK:AbortGroup(PlayerGroup) -local PlayerGroups=self:GetGroups() -if PlayerGroups:IsIncludeObject(PlayerGroup)then -if self:IsStateAssigned()then -local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup) -if IsGroupAssigned then -local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName() -self:UnAssignFromGroup(PlayerGroup) -PlayerGroups:Flush(self) -local IsRemaining=false -for GroupName,AssignedGroup in pairs(PlayerGroups:GetSet()or{})do -if self:IsGroupAssigned(AssignedGroup)==true then -IsRemaining=true -self:F({Task=self:GetName(),IsRemaining=IsRemaining}) -break -end -end -self:F({Task=self:GetName(),IsRemaining=IsRemaining}) -if IsRemaining==false then -self:Abort() -end -self:PlayerAborted(PlayerGroup:GetUnit(1)) -end -end -end -return self -end -function TASK:CrashGroup(PlayerGroup) -self:F({PlayerGroup=PlayerGroup}) -local PlayerGroups=self:GetGroups() -if PlayerGroups:IsIncludeObject(PlayerGroup)then -if self:IsStateAssigned()then -local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup) -self:F({IsGroupAssigned=IsGroupAssigned}) -if IsGroupAssigned then -local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName() -self:MessageToGroups(PlayerName.." crashed! ") -self:UnAssignFromGroup(PlayerGroup) -PlayerGroups:Flush(self) -local IsRemaining=false -for GroupName,AssignedGroup in pairs(PlayerGroups:GetSet()or{})do -if self:IsGroupAssigned(AssignedGroup)==true then -IsRemaining=true -self:F({Task=self:GetName(),IsRemaining=IsRemaining}) -break -end -end -self:F({Task=self:GetName(),IsRemaining=IsRemaining}) -if IsRemaining==false then -self:Abort() -end -self:PlayerCrashed(PlayerGroup:GetUnit(1)) -end -end -end -return self -end -function TASK:GetMission() -return self.Mission -end -function TASK:GetGroups() -return self.SetGroup -end -function TASK:AddGroups(GroupSet) -GroupSet=GroupSet or SET_GROUP:New() -self.SetGroup:ForEachGroup( -function(GroupItem) -GroupSet:Add(GroupItem:GetName(),GroupItem) -end -) -return GroupSet -end -do -function TASK:IsGroupAssigned(TaskGroup) -local TaskGroupName=TaskGroup:GetName() -if self.AssignedGroups[TaskGroupName]then -return true -end -return false -end -function TASK:SetGroupAssigned(TaskGroup) -local TaskName=self:GetName() -local TaskGroupName=TaskGroup:GetName() -self.AssignedGroups[TaskGroupName]=TaskGroup -self:F(string.format("Task %s is assigned to %s",TaskName,TaskGroupName)) -self:GetMission():SetGroupAssigned(TaskGroup) -local SetAssignedGroups=self:GetGroups() -return self -end -function TASK:ClearGroupAssignment(TaskGroup) -local TaskName=self:GetName() -local TaskGroupName=TaskGroup:GetName() -self.AssignedGroups[TaskGroupName]=nil -self:GetMission():ClearGroupAssignment(TaskGroup) -local SetAssignedGroups=self:GetGroups() -SetAssignedGroups:ForEachGroup( -function(AssignedGroup) -if self:IsGroupAssigned(AssignedGroup)then -else -end -end -) -return self -end -end -do -function TASK:SetAssignMethod(AcceptClass) -local ProcessTemplate=self:GetUnitProcess() -ProcessTemplate:SetProcess("Planned","Accept",AcceptClass) -end -function TASK:AssignToGroup(TaskGroup) -self:F(TaskGroup:GetName()) -local TaskGroupName=TaskGroup:GetName() -local Mission=self:GetMission() -local CommandCenter=Mission:GetCommandCenter() -self:SetGroupAssigned(TaskGroup) -local TaskUnits=TaskGroup:GetUnits() -for UnitID,UnitData in pairs(TaskUnits)do -local TaskUnit=UnitData -local PlayerName=TaskUnit:GetPlayerName() -self:F(PlayerName) -if PlayerName~=nil and PlayerName~=""then -self:AssignToUnit(TaskUnit) -CommandCenter:MessageToGroup( -string.format('Task "%s": Briefing for player (%s):\n%s', -self:GetName(), -PlayerName, -self:GetBriefing() -),TaskGroup -) -end -end -CommandCenter:SetMenu() -self:MenuFlashTaskStatus(TaskGroup,self:GetMission():GetCommandCenter().FlashStatus) -return self -end -function TASK:UnAssignFromGroup(TaskGroup) -self:F2({TaskGroup=TaskGroup:GetName()}) -self:ClearGroupAssignment(TaskGroup) -local TaskUnits=TaskGroup:GetUnits() -for UnitID,UnitData in pairs(TaskUnits)do -local TaskUnit=UnitData -local PlayerName=TaskUnit:GetPlayerName() -if PlayerName~=nil and PlayerName~=""then -self:UnAssignFromUnit(TaskUnit) -end -end -local Mission=self:GetMission() -local CommandCenter=Mission:GetCommandCenter() -CommandCenter:SetMenu() -self:MenuFlashTaskStatus(TaskGroup,false) -end -end -function TASK:HasGroup(FindGroup) -local SetAttackGroup=self:GetGroups() -return SetAttackGroup:FindGroup(FindGroup:GetName()) -end -function TASK:AssignToUnit(TaskUnit) -self:F(TaskUnit:GetName()) -local FsmTemplate=self:GetUnitProcess() -local FsmUnit=self:SetStateMachine(TaskUnit,FsmTemplate:Copy(TaskUnit,self)) -FsmUnit:SetStartState("Planned") -FsmUnit:Accept() -return self -end -function TASK:UnAssignFromUnit(TaskUnit) -self:F(TaskUnit:GetName()) -self:RemoveStateMachine(TaskUnit) -self:RemoveTaskControlMenu(TaskUnit) -return self -end -function TASK:SetTimeOut(Timer) -self:F(Timer) -self.TimeOut=Timer -self:__TimeOut(self.TimeOut) -return self -end -function TASK:MessageToGroups(Message) -self:F({Message=Message}) -local Mission=self:GetMission() -local CC=Mission:GetCommandCenter() -for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do -TaskGroup=TaskGroup -if TaskGroup:IsAlive()==true then -CC:MessageToGroup(Message,TaskGroup,TaskGroup:GetName()) -end -end -end -function TASK:SendBriefingToAssignedGroups() -self:F2() -for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do -if TaskGroup:IsAlive()then -if self:IsGroupAssigned(TaskGroup)then -TaskGroup:Message(self.TaskBriefing,60) -end -end -end -end -function TASK:UnAssignFromGroups() -self:F2() -for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do -if TaskGroup:IsAlive()==true then -if self:IsGroupAssigned(TaskGroup)then -self:UnAssignFromGroup(TaskGroup) -end -end -end -end -function TASK:HasAliveUnits() -self:F() -for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do -if TaskGroup:IsAlive()==true then -if self:IsStateAssigned()then -if self:IsGroupAssigned(TaskGroup)then -for TaskUnitID,TaskUnit in pairs(TaskGroup:GetUnits())do -if TaskUnit:IsAlive()then -self:T({HasAliveUnits=true}) -return true -end -end -end -end -end -end -self:T({HasAliveUnits=false}) -return false -end -function TASK:SetMenu(MenuTime) -self:F({self:GetName(),MenuTime}) -for TaskGroupID,TaskGroupData in pairs(self.SetGroup:GetSet())do -local TaskGroup=TaskGroupData -if TaskGroup:IsAlive()==true and TaskGroup:GetPlayerNames()then -local Mission=self:GetMission() -local MissionMenu=Mission:GetMenu(TaskGroup) -if MissionMenu then -self:SetMenuForGroup(TaskGroup,MenuTime) -end -end -end -end -function TASK:SetMenuForGroup(TaskGroup,MenuTime) -if self:IsStatePlanned()or self:IsStateAssigned()then -self:SetPlannedMenuForGroup(TaskGroup,MenuTime) -if self:IsGroupAssigned(TaskGroup)then -self:SetAssignedMenuForGroup(TaskGroup,MenuTime) -end -end -end -function TASK:SetPlannedMenuForGroup(TaskGroup,MenuTime) -self:F(TaskGroup:GetName()) -local Mission=self:GetMission() -local MissionName=Mission:GetName() -local MissionMenu=Mission:GetMenu(TaskGroup) -local TaskType=self:GetType() -local TaskPlayerCount=self:GetPlayerCount() -local TaskPlayerString=string.format(" (%dp)",TaskPlayerCount) -local TaskText=string.format("%s",self:GetName()) -local TaskName=string.format("%s",self:GetName()) -self.MenuPlanned=self.MenuPlanned or{} -self.MenuPlanned[TaskGroup]=MENU_GROUP_DELAYED:New(TaskGroup,"Join Planned Task",MissionMenu,Mission.MenuReportTasksPerStatus,Mission,TaskGroup,"Planned"):SetTime(MenuTime):SetTag("Tasking") -local TaskTypeMenu=MENU_GROUP_DELAYED:New(TaskGroup,TaskType,self.MenuPlanned[TaskGroup]):SetTime(MenuTime):SetTag("Tasking") -local TaskTypeMenu=MENU_GROUP_DELAYED:New(TaskGroup,TaskText,TaskTypeMenu):SetTime(MenuTime):SetTag("Tasking") -if not Mission:IsGroupAssigned(TaskGroup)then -local JoinTaskMenu=MENU_GROUP_COMMAND_DELAYED:New(TaskGroup,string.format("Join Task"),TaskTypeMenu,self.MenuAssignToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") -local MarkTaskMenu=MENU_GROUP_COMMAND_DELAYED:New(TaskGroup,string.format("Mark Task Location on Map"),TaskTypeMenu,self.MenuMarkToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") -end -local ReportTaskMenu=MENU_GROUP_COMMAND_DELAYED:New(TaskGroup,string.format("Report Task Details"),TaskTypeMenu,self.MenuTaskStatus,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") -return self -end -function TASK:SetAssignedMenuForGroup(TaskGroup,MenuTime) -self:F({TaskGroup:GetName(),MenuTime}) -local TaskType=self:GetType() -local TaskPlayerCount=self:GetPlayerCount() -local TaskPlayerString=string.format(" (%dp)",TaskPlayerCount) -local TaskText=string.format("%s%s",self:GetName(),TaskPlayerString) -local TaskName=string.format("%s",self:GetName()) -for UnitName,TaskUnit in pairs(TaskGroup:GetPlayerUnits())do -local TaskUnit=TaskUnit -if TaskUnit then -local MenuControl=self:GetTaskControlMenu(TaskUnit) -local TaskControl=MENU_GROUP:New(TaskGroup,"Control Task",MenuControl):SetTime(MenuTime):SetTag("Tasking") -if self:IsStateAssigned()then -local TaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Abort Task"),TaskControl,self.MenuTaskAbort,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") -end -local MarkMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Mark Task Location on Map"),TaskControl,self.MenuMarkToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") -local TaskTypeMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Report Task Details"),TaskControl,self.MenuTaskStatus,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") -if not self.FlashTaskStatus then -local TaskFlashStatusMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Flash Task Details"),TaskControl,self.MenuFlashTaskStatus,self,TaskGroup,true):SetTime(MenuTime):SetTag("Tasking") -else -local TaskFlashStatusMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Stop Flash Task Details"),TaskControl,self.MenuFlashTaskStatus,self,TaskGroup,nil):SetTime(MenuTime):SetTag("Tasking") -end -end -end -return self -end -function TASK:RemoveMenu(MenuTime) -self:F({self:GetName(),MenuTime}) -for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do -if TaskGroup:IsAlive()==true then -local TaskGroup=TaskGroup -if TaskGroup:IsAlive()==true and TaskGroup:GetPlayerNames()then -self:RefreshMenus(TaskGroup,MenuTime) -end -end -end -end -function TASK:RefreshMenus(TaskGroup,MenuTime) -self:F({TaskGroup:GetName(),MenuTime}) -local Mission=self:GetMission() -local MissionName=Mission:GetName() -local MissionMenu=Mission:GetMenu(TaskGroup) -local TaskName=self:GetName() -self.MenuPlanned=self.MenuPlanned or{} -local PlannedMenu=self.MenuPlanned[TaskGroup] -self.MenuAssigned=self.MenuAssigned or{} -local AssignedMenu=self.MenuAssigned[TaskGroup] -if PlannedMenu then -self.MenuPlanned[TaskGroup]=PlannedMenu:Remove(MenuTime,"Tasking") -PlannedMenu:Set() -end -if AssignedMenu then -self.MenuAssigned[TaskGroup]=AssignedMenu:Remove(MenuTime,"Tasking") -AssignedMenu:Set() -end -end -function TASK:RemoveAssignedMenuForGroup(TaskGroup) -self:F() -local Mission=self:GetMission() -local MissionName=Mission:GetName() -local MissionMenu=Mission:GetMenu(TaskGroup) -if MissionMenu then -MissionMenu:RemoveSubMenus() -end -end -function TASK:MenuAssignToGroup(TaskGroup) -self:F("Join Task menu selected") -self:AssignToGroup(TaskGroup) -end -function TASK:MenuMarkToGroup(TaskGroup) -self:F() -self:UpdateTaskInfo(self.DetectedItem) -local TargetCoordinates=self.TaskInfo:GetData("Coordinates") -if TargetCoordinates then -for TargetCoordinateID,TargetCoordinate in pairs(TargetCoordinates)do -local Report=REPORT:New():SetIndent(0) -self.TaskInfo:Report(Report,"M",TaskGroup,self) -local MarkText=Report:Text(", ") -self:F({Coordinate=TargetCoordinate,MarkText=MarkText}) -TargetCoordinate:MarkToGroup(MarkText,TaskGroup) -end -else -local TargetCoordinate=self.TaskInfo:GetData("Coordinate") -if TargetCoordinate then -local Report=REPORT:New():SetIndent(0) -self.TaskInfo:Report(Report,"M",TaskGroup,self) -local MarkText=Report:Text(", ") -self:F({Coordinate=TargetCoordinate,MarkText=MarkText}) -TargetCoordinate:MarkToGroup(MarkText,TaskGroup) -end -end -end -function TASK:MenuTaskStatus(TaskGroup) -if TaskGroup:IsAlive()then -local ReportText=self:ReportDetails(TaskGroup) -self:T(ReportText) -self:GetMission():GetCommandCenter():MessageTypeToGroup(ReportText,TaskGroup,MESSAGE.Type.Detailed) -end -end -function TASK:MenuFlashTaskStatus(TaskGroup,Flash) -self.FlashTaskStatus=Flash -if self.FlashTaskStatus then -self.FlashTaskScheduler,self.FlashTaskScheduleID=SCHEDULER:New(self,self.MenuTaskStatus,{TaskGroup},0,60) -else -if self.FlashTaskScheduler then -self.FlashTaskScheduler:Stop(self.FlashTaskScheduleID) -self.FlashTaskScheduler=nil -self.FlashTaskScheduleID=nil -end -end -end -function TASK:MenuTaskAbort(TaskGroup) -self:AbortGroup(TaskGroup) -end -function TASK:GetTaskName() -return self.TaskName -end -function TASK:GetTaskBriefing() -return self.TaskBriefing -end -function TASK:GetProcessTemplate(ProcessName) -local ProcessTemplate=self.ProcessClasses[ProcessName] -return ProcessTemplate -end -function TASK:FailProcesses(TaskUnitName) -for ProcessID,ProcessData in pairs(self.Processes[TaskUnitName])do -local Process=ProcessData -Process.Fsm:Fail() -end -end -function TASK:SetStateMachine(TaskUnit,Fsm) -self:F2({TaskUnit,self.Fsm[TaskUnit]~=nil,Fsm:GetClassNameAndID()}) -self.Fsm[TaskUnit]=Fsm -return Fsm -end -function TASK:GetStateMachine(TaskUnit) -self:F2({TaskUnit,self.Fsm[TaskUnit]~=nil}) -return self.Fsm[TaskUnit] -end -function TASK:RemoveStateMachine(TaskUnit) -self:F({TaskUnit=TaskUnit:GetName(),HasFsm=(self.Fsm[TaskUnit]~=nil)}) -if self.Fsm[TaskUnit]then -self.Fsm[TaskUnit]:Remove() -self.Fsm[TaskUnit]=nil -end -collectgarbage() -self:F("Garbage Collected, Processes should be finalized now ...") -end -function TASK:HasStateMachine(TaskUnit) -self:F({TaskUnit,self.Fsm[TaskUnit]~=nil}) -return(self.Fsm[TaskUnit]~=nil) -end -function TASK:GetScoring() -return self.Mission:GetScoring() -end -function TASK:GetTaskIndex() -local TaskType=self:GetType() -local TaskName=self:GetName() -return TaskType.."."..TaskName -end -function TASK:SetName(TaskName) -self.TaskName=TaskName -end -function TASK:GetName() -return self.TaskName -end -function TASK:SetType(TaskType) -self.TaskType=TaskType -end -function TASK:GetType() -return self.TaskType -end -function TASK:SetID(TaskID) -self.TaskID=TaskID -end -function TASK:GetID() -return self.TaskID -end -function TASK:StateSuccess() -self:SetState(self,"State","Success") -return self -end -function TASK:IsStateSuccess() -return self:Is("Success") -end -function TASK:StateFailed() -self:SetState(self,"State","Failed") -return self -end -function TASK:IsStateFailed() -return self:Is("Failed") -end -function TASK:StatePlanned() -self:SetState(self,"State","Planned") -return self -end -function TASK:IsStatePlanned() -return self:Is("Planned") -end -function TASK:StateAborted() -self:SetState(self,"State","Aborted") -return self -end -function TASK:IsStateAborted() -return self:Is("Aborted") -end -function TASK:StateCancelled() -self:SetState(self,"State","Cancelled") -return self -end -function TASK:IsStateCancelled() -return self:Is("Cancelled") -end -function TASK:StateAssigned() -self:SetState(self,"State","Assigned") -return self -end -function TASK:IsStateAssigned() -return self:Is("Assigned") -end -function TASK:StateHold() -self:SetState(self,"State","Hold") -return self -end -function TASK:IsStateHold() -return self:Is("Hold") -end -function TASK:StateReplanned() -self:SetState(self,"State","Replanned") -return self -end -function TASK:IsStateReplanned() -return self:Is("Replanned") -end -function TASK:GetStateString() -return self:GetState(self,"State") -end -function TASK:SetBriefing(TaskBriefing) -self:F(TaskBriefing) -self.TaskBriefing=TaskBriefing -return self -end -function TASK:GetBriefing() -return self.TaskBriefing -end -function TASK:onenterAssigned(From,Event,To,PlayerUnit,PlayerName) -if From~="Assigned"then -local PlayerNames=self:GetPlayerNames() -local PlayerText=REPORT:New() -for PlayerName,TaskName in pairs(PlayerNames)do -PlayerText:Add(PlayerName) -end -self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." is assigned to players "..PlayerText:Text(",")..". Good Luck!") -self:SetGoalTotal() -if self.Dispatcher then -self:F("Firing Assign event ") -self.Dispatcher:Assign(self,PlayerUnit,PlayerName) -end -self:GetMission():__Start(1) -self:__Goal(-10,PlayerUnit,PlayerName) -self:SetMenu() -self:F({"--> Task Assigned",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) -self:F({"--> Task Player Names",PlayerNames=PlayerNames}) -end -end -function TASK:onenterSuccess(From,Event,To) -self:F({"<-> Task Replanned",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) -self:F({"<-> Task Player Names",PlayerNames=self:GetPlayerNames()}) -self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." is successful! Good job!") -self:UnAssignFromGroups() -self:GetMission():__MissionGoals(1) -end -function TASK:onenterAborted(From,Event,To) -self:F({"<-- Task Aborted",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) -self:F({"<-- Task Player Names",PlayerNames=self:GetPlayerNames()}) -if From~="Aborted"then -self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." has been aborted! Task may be replanned.") -self:__Replan(5) -self:SetMenu() -end -end -function TASK:onenterCancelled(From,Event,To) -self:F({"<-- Task Cancelled",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) -self:F({"<-- Player Names",PlayerNames=self:GetPlayerNames()}) -if From~="Cancelled"then -self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." has been cancelled! The tactical situation has changed.") -self:UnAssignFromGroups() -self:SetMenu() -end -end -function TASK:onafterReplan(From,Event,To) -self:F({"Task Replanned",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) -self:F({"Task Player Names",PlayerNames=self:GetPlayerNames()}) -self:GetMission():GetCommandCenter():MessageToCoalition("Replanning Task "..self:GetName()..".") -self:SetMenu() -end -function TASK:onenterFailed(From,Event,To) -self:F({"Task Failed",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) -self:F({"Task Player Names",PlayerNames=self:GetPlayerNames()}) -self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." has failed!") -self:UnAssignFromGroups() -end -function TASK:onstatechange(From,Event,To) -if self:IsTrace()then -end -if self.Scores[To]then -local Scoring=self:GetScoring() -if Scoring then -self:F({self.Scores[To].ScoreText,self.Scores[To].Score}) -Scoring:_AddMissionScore(self.Mission,self.Scores[To].ScoreText,self.Scores[To].Score) -end -end -end -function TASK:onenterPlanned(From,Event,To) -if not self.TimeOut==0 then -self.__TimeOut(self.TimeOut) -end -end -function TASK:onbeforeTimeOut(From,Event,To) -if From=="Planned"then -self:RemoveMenu() -return true -end -return false -end -do -function TASK:SetGoal(Goal) -self.Goal=Goal -end -function TASK:GetGoal() -return self.Goal -end -function TASK:SetDispatcher(Dispatcher) -self.Dispatcher=Dispatcher -end -function TASK:SetDetection(Detection,DetectedItem) -self:F({DetectedItem,Detection}) -self.Detection=Detection -self.DetectedItem=DetectedItem -end -end -do -function TASK:ReportSummary(ReportGroup) -self:UpdateTaskInfo(self.DetectedItem) -local Report=REPORT:New() -Report:Add("Task "..self:GetName()) -Report:Add("State: <"..self:GetState()..">") -self.TaskInfo:Report(Report,"S",ReportGroup,self) -return Report:Text(', ') -end -function TASK:ReportOverview(ReportGroup) -self:UpdateTaskInfo(self.DetectedItem) -local TaskName=self:GetName() -local Report=REPORT:New() -self.TaskInfo:Report(Report,"O",ReportGroup,self) -return Report:Text() -end -function TASK:GetPlayerCount() -local PlayerCount=0 -for TaskGroupID,PlayerGroup in pairs(self:GetGroups():GetSet())do -local PlayerGroup=PlayerGroup -if PlayerGroup:IsAlive()==true then -if self:IsGroupAssigned(PlayerGroup)then -local PlayerNames=PlayerGroup:GetPlayerNames() -PlayerCount=PlayerCount+((PlayerNames)and#PlayerNames or 0) -end -end -end -return PlayerCount -end -function TASK:GetPlayerNames() -local PlayerNameMap={} -for TaskGroupID,PlayerGroup in pairs(self:GetGroups():GetSet())do -local PlayerGroup=PlayerGroup -if PlayerGroup:IsAlive()==true then -if self:IsGroupAssigned(PlayerGroup)then -local PlayerNames=PlayerGroup:GetPlayerNames() -for PlayerNameID,PlayerName in pairs(PlayerNames or{})do -PlayerNameMap[PlayerName]=PlayerGroup -end -end -end -end -return PlayerNameMap -end -function TASK:ReportDetails(ReportGroup) -self:UpdateTaskInfo(self.DetectedItem) -local Report=REPORT:New():SetIndent(3) -local Name=self:GetName() -local Status="<"..self:GetState()..">" -Report:Add("Task "..Name.." - "..Status.." - Detailed Report") -local PlayerNames=self:GetPlayerNames() -local PlayerReport=REPORT:New() -for PlayerName,PlayerGroup in pairs(PlayerNames)do -PlayerReport:Add("Players group "..PlayerGroup:GetCallsign()..": "..PlayerName) -end -local Players=PlayerReport:Text() -if Players~=""then -Report:AddIndent("Players assigned:","-") -Report:AddIndent(Players) -end -self.TaskInfo:Report(Report,"D",ReportGroup,self) -return Report:Text() -end -end -do -function TASK:AddProgress(PlayerName,ProgressText,ProgressTime,ProgressPoints) -self.TaskProgress=self.TaskProgress or{} -self.TaskProgress[ProgressTime]=self.TaskProgress[ProgressTime]or{} -self.TaskProgress[ProgressTime].PlayerName=PlayerName -self.TaskProgress[ProgressTime].ProgressText=ProgressText -self.TaskProgress[ProgressTime].ProgressPoints=ProgressPoints -self:GetMission():AddPlayerName(PlayerName) -return self -end -function TASK:GetPlayerProgress(PlayerName) -local ProgressPlayer=0 -for ProgressTime,ProgressData in pairs(self.TaskProgress)do -if PlayerName==ProgressData.PlayerName then -ProgressPlayer=ProgressPlayer+ProgressData.ProgressPoints -end -end -return ProgressPlayer -end -function TASK:SetScoreOnProgress(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScoreProcess("Engaging","Account","AccountPlayer","Player "..PlayerName.." has achieved progress.",Score) -return self -end -function TASK:SetScoreOnSuccess(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Success","The task is a success!",Score) -return self -end -function TASK:SetScoreOnFail(PlayerName,Penalty,TaskUnit) -self:F({PlayerName,Penalty,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Failed","The task is a failure!",Penalty) -return self -end -end -do -function TASK:InitTaskControlMenu(TaskUnit) -self.TaskControlMenuTime=timer.getTime() -return self.TaskControlMenuTime -end -function TASK:GetTaskControlMenu(TaskUnit,TaskName) -TaskName=TaskName or"" -local TaskGroup=TaskUnit:GetGroup() -local TaskPlayerCount=TaskGroup:GetPlayerCount() -if TaskPlayerCount<=1 then -self.TaskControlMenu=MENU_GROUP:New(TaskUnit:GetGroup(),"Task "..self:GetName().." control"):SetTime(self.TaskControlMenuTime) -else -self.TaskControlMenu=MENU_GROUP:New(TaskUnit:GetGroup(),"Task "..self:GetName().." control for "..TaskUnit:GetPlayerName()):SetTime(self.TaskControlMenuTime) -end -return self.TaskControlMenu -end -function TASK:RemoveTaskControlMenu(TaskUnit) -if self.TaskControlMenu then -self.TaskControlMenu:Remove() -self.TaskControlMenu=nil -end -end -function TASK:RefreshTaskControlMenu(TaskUnit,MenuTime,MenuTag) -if self.TaskControlMenu then -self.TaskControlMenu:Remove(MenuTime,MenuTag) -end -end -end -TASKINFO={ -ClassName="TASKINFO", -} -TASKINFO.Detail="" -function TASKINFO:New(Task) -local self=BASE:Inherit(self,BASE:New()) -self.Task=Task -self.VolatileInfo=SET_BASE:New() -self.PersistentInfo=SET_BASE:New() -self.Info=self.VolatileInfo -return self -end -function TASKINFO:AddInfo(Key,Data,Order,Detail,Keep,ShowKey,Type) -self.VolatileInfo:Add(Key,{Data=Data,Order=Order,Detail=Detail,ShowKey=ShowKey,Type=Type}) -if Keep==true then -self.PersistentInfo:Add(Key,{Data=Data,Order=Order,Detail=Detail,ShowKey=ShowKey,Type=Type}) -end -return self -end -function TASKINFO:GetInfo(Key) -local Object=self:Get(Key) -return Object.Data,Object.Order,Object.Detail -end -function TASKINFO:GetData(Key) -local Object=self.Info:Get(Key) -return Object and Object.Data -end -function TASKINFO:AddText(Key,Text,Order,Detail,Keep) -self:AddInfo(Key,Text,Order,Detail,Keep) -return self -end -function TASKINFO:AddTaskName(Order,Detail,Keep) -self:AddInfo("TaskName",self.Task:GetName(),Order,Detail,Keep) -return self -end -function TASKINFO:AddCoordinate(Coordinate,Order,Detail,Keep,ShowKey,Name) -self:AddInfo(Name or"Coordinate",Coordinate,Order,Detail,Keep,ShowKey,"Coordinate") -return self -end -function TASKINFO:GetCoordinate(Name) -return self:GetData(Name or"Coordinate") -end -function TASKINFO:AddCoordinates(Coordinates,Order,Detail,Keep) -self:AddInfo("Coordinates",Coordinates,Order,Detail,Keep) -return self -end -function TASKINFO:AddThreat(ThreatText,ThreatLevel,Order,Detail,Keep) -self:AddInfo("Threat"," ["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]:"..ThreatText,Order,Detail,Keep) -return self -end -function TASKINFO:GetThreat() -self:GetInfo("Threat") -return self -end -function TASKINFO:AddTargetCount(TargetCount,Order,Detail,Keep) -self:AddInfo("Counting",string.format("%d",TargetCount),Order,Detail,Keep) -return self -end -function TASKINFO:AddTargets(TargetCount,TargetTypes,Order,Detail,Keep) -self:AddInfo("Targets",string.format("%d of %s",TargetCount,TargetTypes),Order,Detail,Keep) -return self -end -function TASKINFO:GetTargets() -self:GetInfo("Targets") -return self -end -function TASKINFO:AddQFEAtCoordinate(Coordinate,Order,Detail,Keep) -self:AddInfo("QFE",Coordinate,Order,Detail,Keep) -return self -end -function TASKINFO:AddTemperatureAtCoordinate(Coordinate,Order,Detail,Keep) -self:AddInfo("Temperature",Coordinate,Order,Detail,Keep) -return self -end -function TASKINFO:AddWindAtCoordinate(Coordinate,Order,Detail,Keep) -self:AddInfo("Wind",Coordinate,Order,Detail,Keep) -return self -end -function TASKINFO:AddCargo(Cargo,Order,Detail,Keep) -self:AddInfo("Cargo",Cargo,Order,Detail,Keep) -return self -end -function TASKINFO:AddCargoSet(SetCargo,Order,Detail,Keep) -local CargoReport=REPORT:New() -CargoReport:Add("") -SetCargo:ForEachCargo( -function(Cargo) -CargoReport:Add(string.format(' - %s (%s) %s - status %s ',Cargo:GetName(),Cargo:GetType(),Cargo:GetTransportationMethod(),Cargo:GetCurrentState())) -end -) -self:AddInfo("Cargo",CargoReport:Text(),Order,Detail,Keep) -return self -end -function TASKINFO:Report(Report,Detail,ReportGroup,Task) -local Line=0 -local LineReport=REPORT:New() -if not self.Task:IsStatePlanned()and not self.Task:IsStateAssigned()then -self.Info=self.PersistentInfo -end -for Key,Data in UTILS.spairs(self.Info.Set,function(t,a,b)return t[a].Order0 then -local TargetSetUnit=SET_UNIT:New() -TargetSetUnit:SetDatabase(DetectedSet) -TargetSetUnit:FilterHasSEAD() -TargetSetUnit:FilterOnce() -return TargetSetUnit -end -return nil -end -function TASK_A2G_DISPATCHER:EvaluateCAS(DetectedItem) -self:F({DetectedItem.ItemID}) -local DetectedSet=DetectedItem.Set -local DetectedZone=DetectedItem.Zone -local GroundUnitCount=DetectedSet:HasGroundUnits() -local FriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) -local RadarCount=DetectedSet:HasSEAD() -if RadarCount==0 and GroundUnitCount>0 and FriendliesNearBy==true then -local TargetSetUnit=SET_UNIT:New() -TargetSetUnit:SetDatabase(DetectedSet) -TargetSetUnit:FilterOnce() -return TargetSetUnit -end -return nil -end -function TASK_A2G_DISPATCHER:EvaluateBAI(DetectedItem,FriendlyCoalition) -self:F({DetectedItem.ItemID}) -local DetectedSet=DetectedItem.Set -local DetectedZone=DetectedItem.Zone -local GroundUnitCount=DetectedSet:HasGroundUnits() -local FriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) -local RadarCount=DetectedSet:HasSEAD() -if RadarCount==0 and GroundUnitCount>0 and FriendliesNearBy==false then -local TargetSetUnit=SET_UNIT:New() -TargetSetUnit:SetDatabase(DetectedSet) -TargetSetUnit:FilterOnce() -return TargetSetUnit -end -return nil -end -function TASK_A2G_DISPATCHER:RemoveTask(TaskIndex) -self.Mission:RemoveTask(self.Tasks[TaskIndex]) -self.Tasks[TaskIndex]=nil -end -function TASK_A2G_DISPATCHER:EvaluateRemoveTask(Mission,Task,TaskIndex,DetectedItemChanged) -if Task then -if(Task:IsStatePlanned()and DetectedItemChanged==true)or Task:IsStateCancelled()then -self:RemoveTask(TaskIndex) -end -end -return Task -end -function TASK_A2G_DISPATCHER:ProcessDetected(Detection) -self:F() -local AreaMsg={} -local TaskMsg={} -local ChangeMsg={} -local Mission=self.Mission -if Mission:IsIDLE()or Mission:IsENGAGED()then -local TaskReport=REPORT:New() -for TaskIndex,TaskData in pairs(self.Tasks)do -local Task=TaskData -if Task:IsStatePlanned()then -local DetectedItem=Detection:GetDetectedItemByIndex(TaskIndex) -if not DetectedItem then -local TaskText=Task:GetName() -for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do -if self.FlashNewTask then -Mission:GetCommandCenter():MessageToGroup(string.format("Obsolete A2G task %s for %s removed.",TaskText,Mission:GetShortText()),TaskGroup) -end -end -Task=self:RemoveTask(TaskIndex) -end -end -end -for DetectedItemID,DetectedItem in pairs(Detection:GetDetectedItems())do -local DetectedItem=DetectedItem -local DetectedSet=DetectedItem.Set -local DetectedZone=DetectedItem.Zone -local DetectedItemID=DetectedItem.ID -local TaskIndex=DetectedItem.Index -local DetectedItemChanged=DetectedItem.Changed -self:F({DetectedItemChanged=DetectedItemChanged,DetectedItemID=DetectedItemID,TaskIndex=TaskIndex}) -local Task=self.Tasks[TaskIndex] -if Task then -if Task:IsStateAssigned()then -if DetectedItemChanged==true then -local TargetsReport=REPORT:New() -local TargetSetUnit=self:EvaluateSEAD(DetectedItem) -if TargetSetUnit then -if Task:IsInstanceOf(TASK_A2G_SEAD)then -Task:SetTargetSetUnit(TargetSetUnit) -Task:SetDetection(Detection,DetectedItem) -Task:UpdateTaskInfo(DetectedItem) -TargetsReport:Add(Detection:GetChangeText(DetectedItem)) -else -Task:Cancel() -end -else -local TargetSetUnit=self:EvaluateCAS(DetectedItem) -if TargetSetUnit then -if Task:IsInstanceOf(TASK_A2G_CAS)then -Task:SetTargetSetUnit(TargetSetUnit) -Task:SetDetection(Detection,DetectedItem) -Task:UpdateTaskInfo(DetectedItem) -TargetsReport:Add(Detection:GetChangeText(DetectedItem)) -else -Task:Cancel() -Task=self:RemoveTask(TaskIndex) -end -else -local TargetSetUnit=self:EvaluateBAI(DetectedItem) -if TargetSetUnit then -if Task:IsInstanceOf(TASK_A2G_BAI)then -Task:SetTargetSetUnit(TargetSetUnit) -Task:SetDetection(Detection,DetectedItem) -Task:UpdateTaskInfo(DetectedItem) -TargetsReport:Add(Detection:GetChangeText(DetectedItem)) -else -Task:Cancel() -Task=self:RemoveTask(TaskIndex) -end -end -end -end -for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do -local TargetsText=TargetsReport:Text(", ") -if(Mission:IsGroupAssigned(TaskGroup))and TargetsText~=""and self.FlashNewTask then -Mission:GetCommandCenter():MessageToGroup(string.format("Task %s has change of targets:\n %s",Task:GetName(),TargetsText),TaskGroup) -end -end -end -end -end -if Task then -if Task:IsStatePlanned()then -if DetectedItemChanged==true then -if Task:IsInstanceOf(TASK_A2G_SEAD)then -local TargetSetUnit=self:EvaluateSEAD(DetectedItem) -if TargetSetUnit then -Task:SetTargetSetUnit(TargetSetUnit) -Task:SetDetection(Detection,DetectedItem) -Task:UpdateTaskInfo(DetectedItem) -else -Task:Cancel() -Task=self:RemoveTask(TaskIndex) -end -else -if Task:IsInstanceOf(TASK_A2G_CAS)then -local TargetSetUnit=self:EvaluateCAS(DetectedItem) -if TargetSetUnit then -Task:SetTargetSetUnit(TargetSetUnit) -Task:SetDetection(Detection,DetectedItem) -Task:UpdateTaskInfo(DetectedItem) -else -Task:Cancel() -Task=self:RemoveTask(TaskIndex) -end -else -if Task:IsInstanceOf(TASK_A2G_BAI)then -local TargetSetUnit=self:EvaluateBAI(DetectedItem) -if TargetSetUnit then -Task:SetTargetSetUnit(TargetSetUnit) -Task:SetDetection(Detection,DetectedItem) -Task:UpdateTaskInfo(DetectedItem) -else -Task:Cancel() -Task=self:RemoveTask(TaskIndex) -end -else -Task:Cancel() -Task=self:RemoveTask(TaskIndex) -end -end -end -end -end -end -if not Task then -local TargetSetUnit=self:EvaluateSEAD(DetectedItem) -if TargetSetUnit then -Task=TASK_A2G_SEAD:New(Mission,self.SetGroup,string.format("SEAD.%03d",DetectedItemID),TargetSetUnit) -DetectedItem.DesignateMenuName=string.format("SEAD.%03d",DetectedItemID) -Task:SetDetection(Detection,DetectedItem) -end -if not Task then -local TargetSetUnit=self:EvaluateCAS(DetectedItem) -if TargetSetUnit then -Task=TASK_A2G_CAS:New(Mission,self.SetGroup,string.format("CAS.%03d",DetectedItemID),TargetSetUnit) -DetectedItem.DesignateMenuName=string.format("CAS.%03d",DetectedItemID) -Task:SetDetection(Detection,DetectedItem) -end -if not Task then -local TargetSetUnit=self:EvaluateBAI(DetectedItem,self.Mission:GetCommandCenter():GetPositionable():GetCoalition()) -if TargetSetUnit then -Task=TASK_A2G_BAI:New(Mission,self.SetGroup,string.format("BAI.%03d",DetectedItemID),TargetSetUnit) -DetectedItem.DesignateMenuName=string.format("BAI.%03d",DetectedItemID) -Task:SetDetection(Detection,DetectedItem) -end -end -end -if Task then -self.Tasks[TaskIndex]=Task -Task:SetTargetZone(DetectedZone) -Task:SetDispatcher(self) -Task:UpdateTaskInfo(DetectedItem) -Mission:AddTask(Task) -function Task.OnEnterSuccess(Task,From,Event,To) -self:Success(Task) -end -function Task.OnEnterCancelled(Task,From,Event,To) -self:Cancelled(Task) -end -function Task.OnEnterFailed(Task,From,Event,To) -self:Failed(Task) -end -function Task.OnEnterAborted(Task,From,Event,To) -self:Aborted(Task) -end -TaskReport:Add(Task:GetName()) -else -self:F("This should not happen") -end -end -Detection:AcceptChanges(DetectedItem) -end -Mission:GetCommandCenter():SetMenu() -local TaskText=TaskReport:Text(", ") -for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do -if(not Mission:IsGroupAssigned(TaskGroup))and TaskText~=""and self.FlashNewTask then -Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetShortText(),TaskText),TaskGroup) -end -end -end -return true -end -end -do -TASK_A2G={ -ClassName="TASK_A2G" -} -function TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskType,TaskBriefing) -local self=BASE:Inherit(self,TASK:New(Mission,SetGroup,TaskName,TaskType,TaskBriefing)) -self:F() -self.TargetSetUnit=TargetSetUnit -self.TaskType=TaskType -local Fsm=self:GetUnitProcess() -Fsm:AddTransition("Assigned","RouteToRendezVous","RoutingToRendezVous") -Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtRendezVous"}) -Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtRendezVous"}) -Fsm:AddTransition({"Arrived","RoutingToRendezVous"},"ArriveAtRendezVous","ArrivedAtRendezVous") -Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"Engage","Engaging") -Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"HoldAtRendezVous","HoldingAtRendezVous") -Fsm:AddProcess("Engaging","Account",ACT_ACCOUNT_DEADS:New(),{}) -Fsm:AddTransition("Engaging","RouteToTarget","Engaging") -Fsm:AddProcess("Engaging","RouteToTargetZone",ACT_ROUTE_ZONE:New(),{}) -Fsm:AddProcess("Engaging","RouteToTargetPoint",ACT_ROUTE_POINT:New(),{}) -Fsm:AddTransition("Engaging","RouteToTargets","Engaging") -Fsm:AddTransition("Rejected","Reject","Aborted") -Fsm:AddTransition("Failed","Fail","Failed") -function Fsm:onafterAssigned(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -self:RouteToRendezVous() -end -function Fsm:onafterRouteToRendezVous(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -if Task:GetRendezVousZone(TaskUnit)then -self:__RouteToRendezVousZone(0.1) -else -if Task:GetRendezVousCoordinate(TaskUnit)then -self:__RouteToRendezVousPoint(0.1) -else -self:__ArriveAtRendezVous(0.1) -end -end -end -function Fsm:OnAfterArriveAtRendezVous(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -self:__Engage(0.1) -end -function Fsm:onafterEngage(TaskUnit,Task) -self:F({self}) -self:__Account(0.1) -self:__RouteToTarget(0.1) -self:__RouteToTargets(-10) -end -function Fsm:onafterRouteToTarget(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -if Task:GetTargetZone(TaskUnit)then -self:__RouteToTargetZone(0.1) -else -local TargetUnit=Task.TargetSetUnit:GetFirst() -if TargetUnit then -local Coordinate=TargetUnit:GetPointVec3() -self:T({TargetCoordinate=Coordinate,Coordinate:GetX(),Coordinate:GetY(),Coordinate:GetZ()}) -Task:SetTargetCoordinate(Coordinate,TaskUnit) -end -self:__RouteToTargetPoint(0.1) -end -end -function Fsm:onafterRouteToTargets(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -local TargetUnit=Task.TargetSetUnit:GetFirst() -if TargetUnit then -Task:SetTargetCoordinate(TargetUnit:GetCoordinate(),TaskUnit) -end -self:__RouteToTargets(-10) -end -return self -end -function TASK_A2G:SetTargetSetUnit(TargetSetUnit) -self.TargetSetUnit=TargetSetUnit -end -function TASK_A2G:GetPlannedMenuText() -return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )" -end -function TASK_A2G:SetRendezVousCoordinate(RendezVousCoordinate,RendezVousRange,TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint") -ActRouteRendezVous:SetCoordinate(RendezVousCoordinate) -ActRouteRendezVous:SetRange(RendezVousRange) -end -function TASK_A2G:GetRendezVousCoordinate(TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint") -return ActRouteRendezVous:GetCoordinate(),ActRouteRendezVous:GetRange() -end -function TASK_A2G:SetRendezVousZone(RendezVousZone,TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone") -ActRouteRendezVous:SetZone(RendezVousZone) -end -function TASK_A2G:GetRendezVousZone(TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone") -return ActRouteRendezVous:GetZone() -end -function TASK_A2G:SetTargetCoordinate(TargetCoordinate,TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint") -ActRouteTarget:SetCoordinate(TargetCoordinate) -end -function TASK_A2G:GetTargetCoordinate(TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint") -return ActRouteTarget:GetCoordinate() -end -function TASK_A2G:SetTargetZone(TargetZone,TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") -ActRouteTarget:SetZone(TargetZone) -end -function TASK_A2G:GetTargetZone(TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") -return ActRouteTarget:GetZone() -end -function TASK_A2G:SetGoalTotal() -self.GoalTotal=self.TargetSetUnit:Count() -end -function TASK_A2G:GetGoalTotal() -return self.GoalTotal -end -function TASK_A2G:ReportOrder(ReportGroup) -self:UpdateTaskInfo(self.DetectedItem) -local Coordinate=self.TaskInfo:GetData("Coordinate") -local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate) -return Distance -end -function TASK_A2G:onafterGoal(TaskUnit,From,Event,To) -local TargetSetUnit=self.TargetSetUnit -if TargetSetUnit:Count()==0 then -self:Success() -end -self:__Goal(-10) -end -function TASK_A2G:UpdateTaskInfo(DetectedItem) -if self:IsStatePlanned()or self:IsStateAssigned()then -local TargetCoordinate=DetectedItem and self.Detection:GetDetectedItemCoordinate(DetectedItem)or self.TargetSetUnit:GetFirst():GetCoordinate() -self.TaskInfo:AddTaskName(0,"MSOD") -self.TaskInfo:AddCoordinate(TargetCoordinate,1,"SOD") -local ThreatLevel,ThreatText -if DetectedItem then -ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(DetectedItem) -else -ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G() -end -self.TaskInfo:AddThreat(ThreatText,ThreatLevel,10,"MOD",true) -if self.Detection then -local DetectedItemsCount=self.TargetSetUnit:Count() -local ReportTypes=REPORT:New() -local TargetTypes={} -for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do -local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit) -if not TargetTypes[TargetType]then -TargetTypes[TargetType]=TargetType -ReportTypes:Add(TargetType) -end -end -self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true) -self.TaskInfo:AddTargets(DetectedItemsCount,ReportTypes:Text(", "),20,"D",true) -else -local DetectedItemsCount=self.TargetSetUnit:Count() -local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames() -self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true) -self.TaskInfo:AddTargets(DetectedItemsCount,DetectedItemsTypes,20,"D",true) -end -self.TaskInfo:AddQFEAtCoordinate(TargetCoordinate,30,"MOD") -self.TaskInfo:AddTemperatureAtCoordinate(TargetCoordinate,31,"MD") -self.TaskInfo:AddWindAtCoordinate(TargetCoordinate,32,"MD") -end -end -function TASK_A2G:GetAutoAssignPriority(AutoAssignMethod,CommandCenter,TaskGroup) -if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then -return math.random(1,9) -elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then -local Coordinate=self.TaskInfo:GetData("Coordinate") -local Distance=Coordinate:Get2DDistance(CommandCenter:GetPositionable():GetCoordinate()) -self:F({Distance=Distance}) -return math.floor(Distance) -elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then -return 1 -end -return 0 -end -end -do -TASK_A2G_SEAD={ -ClassName="TASK_A2G_SEAD" -} -function TASK_A2G_SEAD:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) -local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"SEAD",TaskBriefing)) -self:F() -Mission:AddTask(self) -self:SetBriefing(TaskBriefing or"Execute a Suppression of Enemy Air Defenses.") -return self -end -function TASK_A2G_SEAD:SetScoreOnProgress(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has SEADed a target.",Score) -return self -end -function TASK_A2G_SEAD:SetScoreOnSuccess(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Success","All radar emitting targets have been successfully SEADed!",Score) -return self -end -function TASK_A2G_SEAD:SetScoreOnFail(PlayerName,Penalty,TaskUnit) -self:F({PlayerName,Penalty,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Failed","The SEADing has failed!",Penalty) -return self -end -end -do -TASK_A2G_BAI={ClassName="TASK_A2G_BAI"} -function TASK_A2G_BAI:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) -local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"BAI",TaskBriefing)) -self:F() -Mission:AddTask(self) -self:SetBriefing(TaskBriefing or"Execute a Battlefield Air Interdiction of a group of enemy targets.") -return self -end -function TASK_A2G_BAI:SetScoreOnProgress(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has destroyed a target in Battlefield Air Interdiction (BAI).",Score) -return self -end -function TASK_A2G_BAI:SetScoreOnSuccess(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Success","All targets have been successfully destroyed! The Battlefield Air Interdiction (BAI) is a success!",Score) -return self -end -function TASK_A2G_BAI:SetScoreOnFail(PlayerName,Penalty,TaskUnit) -self:F({PlayerName,Penalty,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Failed","The Battlefield Air Interdiction (BAI) has failed!",Penalty) -return self -end -end -do -TASK_A2G_CAS={ClassName="TASK_A2G_CAS"} -function TASK_A2G_CAS:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) -local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"CAS",TaskBriefing)) -self:F() -Mission:AddTask(self) -self:SetBriefing(TaskBriefing or("Execute a Close Air Support for a group of enemy targets. ".."Beware of friendlies at the vicinity! ")) -return self -end -function TASK_A2G_CAS:SetScoreOnProgress(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has destroyed a target in Close Air Support (CAS).",Score) -return self -end -function TASK_A2G_CAS:SetScoreOnSuccess(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Success","All targets have been successfully destroyed! The Close Air Support (CAS) was a success!",Score) -return self -end -function TASK_A2G_CAS:SetScoreOnFail(PlayerName,Penalty,TaskUnit) -self:F({PlayerName,Penalty,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Failed","The Close Air Support (CAS) has failed!",Penalty) -return self -end -end -do -TASK_A2A_DISPATCHER={ -ClassName="TASK_A2A_DISPATCHER", -Mission=nil, -Detection=nil, -Tasks={}, -SweepZones={}, -} -function TASK_A2A_DISPATCHER:New(Mission,SetGroup,Detection) -local self=BASE:Inherit(self,DETECTION_MANAGER:New(SetGroup,Detection)) -self.Detection=Detection -self.Mission=Mission -self.FlashNewTask=false -self.Detection:FilterCategories(Unit.Category.AIRPLANE,Unit.Category.HELICOPTER) -self.Detection:InitDetectRadar(true) -self.Detection:SetRefreshTimeInterval(30) -self:AddTransition("Started","Assign","Started") -self:__Start(5) -return self -end -function TASK_A2A_DISPATCHER:SetEngageRadius(EngageRadius) -self.Detection:SetFriendliesRange(EngageRadius or 100000) -return self -end -function TASK_A2A_DISPATCHER:SetSendMessages(onoff) -self.FlashNewTask=onoff -end -function TASK_A2A_DISPATCHER:EvaluateINTERCEPT(DetectedItem) -self:F({DetectedItem.ItemID}) -local DetectedSet=DetectedItem.Set -local DetectedZone=DetectedItem.Zone -if DetectedItem.IsDetected==true then -local TargetSetUnit=SET_UNIT:New() -TargetSetUnit:SetDatabase(DetectedSet) -TargetSetUnit:FilterOnce() -return TargetSetUnit -end -return nil -end -function TASK_A2A_DISPATCHER:EvaluateSWEEP(DetectedItem) -self:F({DetectedItem.ItemID}) -local DetectedSet=DetectedItem.Set -local DetectedZone=DetectedItem.Zone -if DetectedItem.IsDetected==false then -local TargetSetUnit=SET_UNIT:New() -TargetSetUnit:SetDatabase(DetectedSet) -TargetSetUnit:FilterOnce() -return TargetSetUnit -end -return nil -end -function TASK_A2A_DISPATCHER:EvaluateENGAGE(DetectedItem) -self:F({DetectedItem.ItemID}) -local DetectedSet=DetectedItem.Set -local DetectedZone=DetectedItem.Zone -local PlayersCount,PlayersReport=self:GetPlayerFriendliesNearBy(DetectedItem) -if PlayersCount>0 and DetectedItem.IsDetected==true then -local TargetSetUnit=SET_UNIT:New() -TargetSetUnit:SetDatabase(DetectedSet) -TargetSetUnit:FilterOnce() -return TargetSetUnit -end -return nil -end -function TASK_A2A_DISPATCHER:EvaluateRemoveTask(Mission,Task,Detection,DetectedItem,DetectedItemIndex,DetectedItemChanged) -if Task then -if Task:IsStatePlanned()then -local TaskName=Task:GetName() -local TaskType=TaskName:match("(%u+)%.%d+") -self:T2({TaskType=TaskType}) -local Remove=false -local IsPlayers=Detection:IsPlayersNearBy(DetectedItem) -if TaskType=="ENGAGE"then -if IsPlayers==false then -Remove=true -end -end -if TaskType=="INTERCEPT"then -if IsPlayers==true then -Remove=true -end -if DetectedItem.IsDetected==false then -Remove=true -end -end -if TaskType=="SWEEP"then -if DetectedItem.IsDetected==true then -Remove=true -end -end -local DetectedSet=DetectedItem.Set -if DetectedSet:Count()==0 then -Remove=true -end -if DetectedItemChanged==true or Remove then -Task=self:RemoveTask(DetectedItemIndex) -end -end -end -return Task -end -function TASK_A2A_DISPATCHER:GetFriendliesNearBy(DetectedItem) -local DetectedSet=DetectedItem.Set -local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem,Unit.Category.AIRPLANE) -local FriendlyTypes={} -local FriendliesCount=0 -if FriendlyUnitsNearBy then -local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() -for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do -local FriendlyUnit=FriendlyUnitData -if FriendlyUnit:IsAirPlane()then -local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel() -FriendliesCount=FriendliesCount+1 -local FriendlyType=FriendlyUnit:GetTypeName() -FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1 -if DetectedTreatLevel0 then -for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do -FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType)) -end -else -FriendlyTypesReport:Add("-") -end -return FriendliesCount,FriendlyTypesReport -end -function TASK_A2A_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem) -local DetectedSet=DetectedItem.Set -local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem) -local PlayerTypes={} -local PlayersCount=0 -if PlayersNearBy then -local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() -for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do -local PlayerUnit=PlayerUnitData -local PlayerName=PlayerUnit:GetPlayerName() -if PlayerUnit:IsAirPlane()and PlayerName~=nil then -local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel() -PlayersCount=PlayersCount+1 -local PlayerType=PlayerUnit:GetTypeName() -PlayerTypes[PlayerName]=PlayerType -if DetectedTreatLevel0 then -for PlayerName,PlayerType in pairs(PlayerTypes)do -PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType)) -end -else -PlayerTypesReport:Add("-") -end -return PlayersCount,PlayerTypesReport -end -function TASK_A2A_DISPATCHER:RemoveTask(TaskIndex) -self.Mission:RemoveTask(self.Tasks[TaskIndex]) -self.Tasks[TaskIndex]=nil -end -function TASK_A2A_DISPATCHER:ProcessDetected(Detection) -self:F() -local AreaMsg={} -local TaskMsg={} -local ChangeMsg={} -local Mission=self.Mission -if Mission:IsIDLE()or Mission:IsENGAGED()then -local TaskReport=REPORT:New() -for TaskIndex,TaskData in pairs(self.Tasks)do -local Task=TaskData -if Task:IsStatePlanned()then -local DetectedItem=Detection:GetDetectedItemByIndex(TaskIndex) -if not DetectedItem then -local TaskText=Task:GetName() -for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do -Mission:GetCommandCenter():MessageToGroup(string.format("Obsolete A2A task %s for %s removed.",TaskText,Mission:GetShortText()),TaskGroup) -end -Task=self:RemoveTask(TaskIndex) -end -end -end -for DetectedItemID,DetectedItem in pairs(Detection:GetDetectedItems())do -local DetectedItem=DetectedItem -local DetectedSet=DetectedItem.Set -local DetectedCount=DetectedSet:Count() -local DetectedZone=DetectedItem.Zone -local DetectedID=DetectedItem.ID -local TaskIndex=DetectedItem.Index -local DetectedItemChanged=DetectedItem.Changed -local Task=self.Tasks[TaskIndex] -Task=self:EvaluateRemoveTask(Mission,Task,Detection,DetectedItem,TaskIndex,DetectedItemChanged) -if not Task and DetectedCount>0 then -local TargetSetUnit=self:EvaluateENGAGE(DetectedItem) -if TargetSetUnit then -Task=TASK_A2A_ENGAGE:New(Mission,self.SetGroup,string.format("ENGAGE.%03d",DetectedID),TargetSetUnit) -Task:SetDetection(Detection,DetectedItem) -Task:UpdateTaskInfo(DetectedItem) -else -local TargetSetUnit=self:EvaluateINTERCEPT(DetectedItem) -if TargetSetUnit then -Task=TASK_A2A_INTERCEPT:New(Mission,self.SetGroup,string.format("INTERCEPT.%03d",DetectedID),TargetSetUnit) -Task:SetDetection(Detection,DetectedItem) -Task:UpdateTaskInfo(DetectedItem) -else -local TargetSetUnit=self:EvaluateSWEEP(DetectedItem) -if TargetSetUnit then -Task=TASK_A2A_SWEEP:New(Mission,self.SetGroup,string.format("SWEEP.%03d",DetectedID),TargetSetUnit) -Task:SetDetection(Detection,DetectedItem) -Task:UpdateTaskInfo(DetectedItem) -end -end -end -if Task then -self.Tasks[TaskIndex]=Task -Task:SetTargetZone(DetectedZone,DetectedItem.Coordinate.y,DetectedItem.Coordinate.Heading) -Task:SetDispatcher(self) -Mission:AddTask(Task) -function Task.OnEnterSuccess(Task,From,Event,To) -self:Success(Task) -end -function Task.OnEnterCancelled(Task,From,Event,To) -self:Cancelled(Task) -end -function Task.OnEnterFailed(Task,From,Event,To) -self:Failed(Task) -end -function Task.OnEnterAborted(Task,From,Event,To) -self:Aborted(Task) -end -TaskReport:Add(Task:GetName()) -else -self:F("This should not happen") -end -end -if Task then -local FriendliesCount,FriendliesReport=self:GetFriendliesNearBy(DetectedItem,Unit.Category.AIRPLANE) -Task.TaskInfo:AddText("Friendlies",string.format("%d ( %s )",FriendliesCount,FriendliesReport:Text(",")),40,"MOD") -local PlayersCount,PlayersReport=self:GetPlayerFriendliesNearBy(DetectedItem) -Task.TaskInfo:AddText("Players",string.format("%d ( %s )",PlayersCount,PlayersReport:Text(",")),40,"MOD") -end -Detection:AcceptChanges(DetectedItem) -end -Mission:GetCommandCenter():SetMenu() -local TaskText=TaskReport:Text(", ") -for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do -if(not Mission:IsGroupAssigned(TaskGroup))and TaskText~=""and(self.FlashNewTask)then -Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetShortText(),TaskText),TaskGroup) -end -end -end -return true -end -end -do -TASK_A2A={ -ClassName="TASK_A2A" -} -function TASK_A2A:New(Mission,SetAttack,TaskName,TargetSetUnit,TaskType,TaskBriefing) -local self=BASE:Inherit(self,TASK:New(Mission,SetAttack,TaskName,TaskType,TaskBriefing)) -self:F() -self.TargetSetUnit=TargetSetUnit -self.TaskType=TaskType -local Fsm=self:GetUnitProcess() -Fsm:AddTransition("Assigned","RouteToRendezVous","RoutingToRendezVous") -Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtRendezVous"}) -Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtRendezVous"}) -Fsm:AddTransition({"Arrived","RoutingToRendezVous"},"ArriveAtRendezVous","ArrivedAtRendezVous") -Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"Engage","Engaging") -Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"HoldAtRendezVous","HoldingAtRendezVous") -Fsm:AddProcess("Engaging","Account",ACT_ACCOUNT_DEADS:New(),{}) -Fsm:AddTransition("Engaging","RouteToTarget","Engaging") -Fsm:AddProcess("Engaging","RouteToTargetZone",ACT_ROUTE_ZONE:New(),{}) -Fsm:AddProcess("Engaging","RouteToTargetPoint",ACT_ROUTE_POINT:New(),{}) -Fsm:AddTransition("Engaging","RouteToTargets","Engaging") -Fsm:AddTransition("Rejected","Reject","Aborted") -Fsm:AddTransition("Failed","Fail","Failed") -function Fsm:OnLeaveAssigned(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -self:SelectAction() -end -function Fsm:onafterRouteToRendezVous(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -if Task:GetRendezVousZone(TaskUnit)then -self:__RouteToRendezVousZone(0.1) -else -if Task:GetRendezVousCoordinate(TaskUnit)then -self:__RouteToRendezVousPoint(0.1) -else -self:__ArriveAtRendezVous(0.1) -end -end -end -function Fsm:OnAfterArriveAtRendezVous(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -self:__Engage(0.1) -end -function Fsm:onafterEngage(TaskUnit,Task) -self:F({self}) -self:__Account(0.1) -self:__RouteToTarget(0.1) -self:__RouteToTargets(-10) -end -function Fsm:onafterRouteToTarget(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -if Task:GetTargetZone(TaskUnit)then -self:__RouteToTargetZone(0.1) -else -local TargetUnit=Task.TargetSetUnit:GetFirst() -if TargetUnit then -local Coordinate=TargetUnit:GetPointVec3() -self:T({TargetCoordinate=Coordinate,Coordinate:GetX(),Coordinate:GetAlt(),Coordinate:GetZ()}) -Task:SetTargetCoordinate(Coordinate,TaskUnit) -end -self:__RouteToTargetPoint(0.1) -end -end -function Fsm:onafterRouteToTargets(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -local TargetUnit=Task.TargetSetUnit:GetFirst() -if TargetUnit then -Task:SetTargetCoordinate(TargetUnit:GetCoordinate(),TaskUnit) -end -self:__RouteToTargets(-10) -end -return self -end -function TASK_A2A:SetTargetSetUnit(TargetSetUnit) -self.TargetSetUnit=TargetSetUnit -end -function TASK_A2A:GetPlannedMenuText() -return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )" -end -function TASK_A2A:SetRendezVousCoordinate(RendezVousCoordinate,RendezVousRange,TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint") -ActRouteRendezVous:SetCoordinate(RendezVousCoordinate) -ActRouteRendezVous:SetRange(RendezVousRange) -end -function TASK_A2A:GetRendezVousCoordinate(TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint") -return ActRouteRendezVous:GetCoordinate(),ActRouteRendezVous:GetRange() -end -function TASK_A2A:SetRendezVousZone(RendezVousZone,TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone") -ActRouteRendezVous:SetZone(RendezVousZone) -end -function TASK_A2A:GetRendezVousZone(TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone") -return ActRouteRendezVous:GetZone() -end -function TASK_A2A:SetTargetCoordinate(TargetCoordinate,TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint") -ActRouteTarget:SetCoordinate(TargetCoordinate) -end -function TASK_A2A:GetTargetCoordinate(TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint") -return ActRouteTarget:GetCoordinate() -end -function TASK_A2A:SetTargetZone(TargetZone,Altitude,Heading,TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") -ActRouteTarget:SetZone(TargetZone,Altitude,Heading) -end -function TASK_A2A:GetTargetZone(TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") -return ActRouteTarget:GetZone() -end -function TASK_A2A:SetGoalTotal() -self.GoalTotal=self.TargetSetUnit:Count() -end -function TASK_A2A:GetGoalTotal() -return self.GoalTotal -end -function TASK_A2A:ReportOrder(ReportGroup) -self:UpdateTaskInfo(self.DetectedItem) -local Coordinate=self.TaskInfo:GetData("Coordinate") -local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate) -return Distance -end -function TASK_A2A:onafterGoal(TaskUnit,From,Event,To) -local TargetSetUnit=self.TargetSetUnit -if TargetSetUnit:Count()==0 then -self:Success() -end -self:__Goal(-10) -end -function TASK_A2A:UpdateTaskInfo(DetectedItem) -if self:IsStatePlanned()or self:IsStateAssigned()then -local TargetCoordinate=DetectedItem and self.Detection:GetDetectedItemCoordinate(DetectedItem)or self.TargetSetUnit:GetFirst():GetCoordinate() -self.TaskInfo:AddTaskName(0,"MSOD") -self.TaskInfo:AddCoordinate(TargetCoordinate,1,"SOD") -local ThreatLevel,ThreatText -if DetectedItem then -ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(DetectedItem) -else -ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G() -end -self.TaskInfo:AddThreat(ThreatText,ThreatLevel,10,"MOD",true) -if self.Detection then -local DetectedItemsCount=self.TargetSetUnit:Count() -local ReportTypes=REPORT:New() -local TargetTypes={} -for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do -local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit) -if not TargetTypes[TargetType]then -TargetTypes[TargetType]=TargetType -ReportTypes:Add(TargetType) -end -end -self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true) -self.TaskInfo:AddTargets(DetectedItemsCount,ReportTypes:Text(", "),20,"D",true) -else -local DetectedItemsCount=self.TargetSetUnit:Count() -local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames() -self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true) -self.TaskInfo:AddTargets(DetectedItemsCount,DetectedItemsTypes,20,"D",true) -end -end -end -function TASK_A2A:GetAutoAssignPriority(AutoAssignMethod,CommandCenter,TaskGroup) -if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then -return math.random(1,9) -elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then -local Coordinate=self.TaskInfo:GetData("Coordinate") -local Distance=Coordinate:Get2DDistance(CommandCenter:GetPositionable():GetCoordinate()) -return math.floor(Distance) -elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then -return 1 -end -return 0 -end -end -do -TASK_A2A_INTERCEPT={ -ClassName="TASK_A2A_INTERCEPT" -} -function TASK_A2A_INTERCEPT:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) -local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"INTERCEPT",TaskBriefing)) -self:F() -Mission:AddTask(self) -self:SetBriefing(TaskBriefing or"Intercept incoming intruders.\n") -return self -end -function TASK_A2A_INTERCEPT:SetScoreOnProgress(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has intercepted a target.",Score) -return self -end -function TASK_A2A_INTERCEPT:SetScoreOnSuccess(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Success","All targets have been successfully intercepted!",Score) -return self -end -function TASK_A2A_INTERCEPT:SetScoreOnFail(PlayerName,Penalty,TaskUnit) -self:F({PlayerName,Penalty,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Failed","The intercept has failed!",Penalty) -return self -end -end -do -TASK_A2A_SWEEP={ -ClassName="TASK_A2A_SWEEP" -} -function TASK_A2A_SWEEP:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) -local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"SWEEP",TaskBriefing)) -self:F() -Mission:AddTask(self) -self:SetBriefing(TaskBriefing or"Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n") -return self -end -function TASK_A2A_SWEEP:onafterGoal(TaskUnit,From,Event,To) -local TargetSetUnit=self.TargetSetUnit -if TargetSetUnit:Count()==0 then -self:Success() -end -self:__Goal(-10) -end -function TASK_A2A_SWEEP:SetScoreOnProgress(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has sweeped a target.",Score) -return self -end -function TASK_A2A_SWEEP:SetScoreOnSuccess(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Success","All targets have been successfully sweeped!",Score) -return self -end -function TASK_A2A_SWEEP:SetScoreOnFail(PlayerName,Penalty,TaskUnit) -self:F({PlayerName,Penalty,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Failed","The sweep has failed!",Penalty) -return self -end -end -do -TASK_A2A_ENGAGE={ -ClassName="TASK_A2A_ENGAGE" -} -function TASK_A2A_ENGAGE:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) -local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"ENGAGE",TaskBriefing)) -self:F() -Mission:AddTask(self) -self:SetBriefing(TaskBriefing or"Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n") -return self -end -function TASK_A2A_ENGAGE:SetScoreOnProgress(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has engaged and destroyed a target.",Score) -return self -end -function TASK_A2A_ENGAGE:SetScoreOnSuccess(PlayerName,Score,TaskUnit) -self:F({PlayerName,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Success","All targets have been successfully engaged!",Score) -return self -end -function TASK_A2A_ENGAGE:SetScoreOnFail(PlayerName,Penalty,TaskUnit) -self:F({PlayerName,Penalty,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Failed","The target engagement has failed!",Penalty) -return self -end -end -do -TASK_CARGO={ -ClassName="TASK_CARGO", -} -function TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,TaskType,TaskBriefing) -local self=BASE:Inherit(self,TASK:New(Mission,SetGroup,TaskName,TaskType,TaskBriefing)) -self:F({Mission,SetGroup,TaskName,SetCargo,TaskType}) -self.SetCargo=SetCargo -self.TaskType=TaskType -self.SmokeColor=SMOKECOLOR.Red -self.CargoItemCount={} -self.CargoLimit=10 -self.DeployZones={} -self:AddTransition("*","CargoDeployed","*") -self:AddTransition("*","CargoPickedUp","*") -local Fsm=self:GetUnitProcess() -Fsm:AddTransition({"Planned","Assigned","Cancelled","WaitingForCommand","ArrivedAtPickup","ArrivedAtDeploy","Boarded","UnBoarded","Loaded","UnLoaded","Landed","Boarding"},"SelectAction","*") -Fsm:AddTransition("*","RouteToPickup","RoutingToPickup") -Fsm:AddProcess("RoutingToPickup","RouteToPickupPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtPickup",Cancelled="CancelRouteToPickup"}) -Fsm:AddTransition("Arrived","ArriveAtPickup","ArrivedAtPickup") -Fsm:AddTransition("Cancelled","CancelRouteToPickup","Cancelled") -Fsm:AddTransition("*","RouteToDeploy","RoutingToDeploy") -Fsm:AddProcess("RoutingToDeploy","RouteToDeployZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtDeploy",Cancelled="CancelRouteToDeploy"}) -Fsm:AddTransition("Arrived","ArriveAtDeploy","ArrivedAtDeploy") -Fsm:AddTransition("Cancelled","CancelRouteToDeploy","Cancelled") -Fsm:AddTransition({"ArrivedAtPickup","ArrivedAtDeploy","Landing"},"Land","Landing") -Fsm:AddTransition("Landing","Landed","Landed") -Fsm:AddTransition("*","PrepareBoarding","AwaitBoarding") -Fsm:AddTransition("AwaitBoarding","Board","Boarding") -Fsm:AddTransition("Boarding","Boarded","Boarded") -Fsm:AddTransition("*","Load","Loaded") -Fsm:AddTransition("*","PrepareUnBoarding","AwaitUnBoarding") -Fsm:AddTransition("AwaitUnBoarding","UnBoard","UnBoarding") -Fsm:AddTransition("UnBoarding","UnBoarded","UnBoarded") -Fsm:AddTransition("*","Unload","Unloaded") -Fsm:AddTransition("*","Planned","Planned") -Fsm:AddTransition("Deployed","Success","Success") -Fsm:AddTransition("Rejected","Reject","Aborted") -Fsm:AddTransition("Failed","Fail","Failed") -function Fsm:OnAfterAssigned(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -self:SelectAction() -end -function Fsm:onafterSelectAction(TaskUnit,Task) -local TaskUnitName=TaskUnit:GetName() -local MenuTime=Task:InitTaskControlMenu(TaskUnit) -local MenuControl=Task:GetTaskControlMenu(TaskUnit) -Task.SetCargo:ForEachCargo( -function(Cargo) -if Cargo:IsAlive()then -local TaskGroup=TaskUnit:GetGroup() -if Cargo:IsUnLoaded()then -local CargoBayFreeWeight=TaskUnit:GetCargoBayFreeWeight() -local CargoWeight=Cargo:GetWeight() -self:F({CargoBayFreeWeight=CargoBayFreeWeight}) -if CargoBayFreeWeight>CargoWeight then -if Cargo:IsInReportRadius(TaskUnit:GetPointVec2())then -local NotInDeployZones=true -for DeployZoneName,DeployZone in pairs(Task.DeployZones)do -if Cargo:IsInZone(DeployZone)then -NotInDeployZones=false -end -end -if NotInDeployZones then -if not TaskUnit:InAir()then -if Cargo:CanBoard()==true then -if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then -Cargo:Report("Ready for boarding.","board",TaskUnit:GetGroup()) -local BoardMenu=MENU_GROUP:New(TaskGroup,"Board cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") -MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,BoardMenu,self.MenuBoardCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() -else -Cargo:Report("Board at "..Cargo:GetCoordinate():ToString(TaskUnit:GetGroup().."."),"reporting",TaskUnit:GetGroup()) -end -else -if Cargo:CanLoad()==true then -if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then -Cargo:Report("Ready for loading.","load",TaskUnit:GetGroup()) -local LoadMenu=MENU_GROUP:New(TaskGroup,"Load cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") -MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,LoadMenu,self.MenuLoadCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() -else -Cargo:Report("Load at "..Cargo:GetCoordinate():ToString(TaskUnit:GetGroup()).." within "..Cargo.NearRadius..".","reporting",TaskUnit:GetGroup()) -end -else -if Cargo:CanSlingload()==true then -if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then -Cargo:Report("Ready for sling loading.","slingload",TaskUnit:GetGroup()) -local SlingloadMenu=MENU_GROUP:New(TaskGroup,"Slingload cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") -MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,SlingloadMenu,self.MenuLoadCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() -else -Cargo:Report("Slingload at "..Cargo:GetCoordinate():ToString(TaskUnit:GetGroup())..".","reporting",TaskUnit:GetGroup()) -end -end -end -end -else -Cargo:ReportResetAll(TaskUnit:GetGroup()) -end -end -else -if not Cargo:IsDeployed()==true then -local RouteToPickupMenu=MENU_GROUP:New(TaskGroup,"Route to pickup cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") -Cargo:ReportResetAll(TaskUnit:GetGroup()) -if Cargo:CanBoard()==true then -if not Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then -local BoardMenu=MENU_GROUP:New(TaskGroup,"Board cargo",RouteToPickupMenu):SetTime(MenuTime):SetTag("Cargo") -MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,BoardMenu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() -end -else -if Cargo:CanLoad()==true then -if not Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then -local LoadMenu=MENU_GROUP:New(TaskGroup,"Load cargo",RouteToPickupMenu):SetTime(MenuTime):SetTag("Cargo") -MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,LoadMenu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() -end -else -if Cargo:CanSlingload()==true then -if not Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then -local SlingloadMenu=MENU_GROUP:New(TaskGroup,"Slingload cargo",RouteToPickupMenu):SetTime(MenuTime):SetTag("Cargo") -MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,SlingloadMenu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() -end -end -end -end -end -end -end -for DeployZoneName,DeployZone in pairs(Task.DeployZones)do -if Cargo:IsInZone(DeployZone)then -Task:I({CargoIsDeployed=Task.CargoDeployed and"true"or"false"}) -if Cargo:IsDeployed()==false then -Cargo:SetDeployed(true) -Task:I({CargoIsAlive=Cargo:IsAlive()and"true"or"false"}) -if Cargo:IsAlive()then -Task:CargoDeployed(TaskUnit,Cargo,DeployZone) -end -end -end -end -end -if Cargo:IsLoaded()==true and Cargo:IsLoadedInCarrier(TaskUnit)==true then -if not TaskUnit:InAir()then -if Cargo:CanUnboard()==true then -local UnboardMenu=MENU_GROUP:New(TaskGroup,"Unboard cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") -MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,UnboardMenu,self.MenuUnboardCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() -else -if Cargo:CanUnload()==true then -local UnloadMenu=MENU_GROUP:New(TaskGroup,"Unload cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") -MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,UnloadMenu,self.MenuUnloadCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() -end -end -end -end -for DeployZoneName,DeployZone in pairs(Task.DeployZones)do -if not Cargo:IsInZone(DeployZone)then -local RouteToDeployMenu=MENU_GROUP:New(TaskGroup,"Route to deploy cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") -MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),"Zone "..DeployZoneName,RouteToDeployMenu,self.MenuRouteToDeploy,self,DeployZone):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() -end -end -end -end -) -Task:RefreshTaskControlMenu(TaskUnit,MenuTime,"Cargo") -self:__SelectAction(-1) -end -function Fsm:OnLeaveWaitingForCommand(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -end -function Fsm:MenuBoardCargo(Cargo) -self:__PrepareBoarding(1.0,Cargo) -end -function Fsm:MenuLoadCargo(Cargo) -self:__Load(1.0,Cargo) -end -function Fsm:MenuUnboardCargo(Cargo,DeployZone) -self:__PrepareUnBoarding(1.0,Cargo,DeployZone) -end -function Fsm:MenuUnloadCargo(Cargo,DeployZone) -self:__Unload(1.0,Cargo,DeployZone) -end -function Fsm:MenuRouteToPickup(Cargo) -self:__RouteToPickup(1.0,Cargo) -end -function Fsm:MenuRouteToDeploy(DeployZone) -self:__RouteToDeploy(1.0,DeployZone) -end -function Fsm:onafterRouteToPickup(TaskUnit,Task,From,Event,To,Cargo) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -if Cargo:IsAlive()then -self.Cargo=Cargo -Task:SetCargoPickup(self.Cargo,TaskUnit) -self:__RouteToPickupPoint(-0.1) -end -end -function Fsm:onafterArriveAtPickup(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -if self.Cargo:IsAlive()then -if TaskUnit:IsAir()then -Task:GetMission():GetCommandCenter():MessageToGroup("Land",TaskUnit:GetGroup()) -self:__Land(-0.1,"Pickup") -else -self:__SelectAction(-0.1) -end -end -end -function Fsm:onafterCancelRouteToPickup(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -Task:GetMission():GetCommandCenter():MessageToGroup("Cancelled routing to Cargo "..self.Cargo:GetName(),TaskUnit:GetGroup()) -self:__SelectAction(-0.1) -end -function Fsm:onafterRouteToDeploy(TaskUnit,Task,From,Event,To,DeployZone) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -self:F(DeployZone) -self.DeployZone=DeployZone -Task:SetDeployZone(self.DeployZone,TaskUnit) -self:__RouteToDeployZone(-0.1) -end -function Fsm:onafterArriveAtDeploy(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -if TaskUnit:IsAir()then -Task:GetMission():GetCommandCenter():MessageToGroup("Land",TaskUnit:GetGroup()) -self:__Land(-0.1,"Deploy") -else -self:__SelectAction(-0.1) -end -end -function Fsm:onafterCancelRouteToDeploy(TaskUnit,Task) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -Task:GetMission():GetCommandCenter():MessageToGroup("Cancelled routing to deploy zone "..self.DeployZone:GetName(),TaskUnit:GetGroup()) -self:__SelectAction(-0.1) -end -function Fsm:onafterLand(TaskUnit,Task,From,Event,To,Action) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -if Action=="Pickup"then -if self.Cargo:IsAlive()then -if self.Cargo:IsInReportRadius(TaskUnit:GetPointVec2())then -if TaskUnit:InAir()then -self:__Land(-10,Action) -else -Task:GetMission():GetCommandCenter():MessageToGroup("Landed at pickup location...",TaskUnit:GetGroup()) -self:__Landed(-0.1,Action) -end -else -self:__RouteToPickup(-0.1,self.Cargo) -end -end -else -if TaskUnit:IsAlive()then -if TaskUnit:IsInZone(self.DeployZone)then -if TaskUnit:InAir()then -self:__Land(-10,Action) -else -Task:GetMission():GetCommandCenter():MessageToGroup("Landed at deploy zone "..self.DeployZone:GetName(),TaskUnit:GetGroup()) -self:__Landed(-0.1,Action) -end -else -self:__RouteToDeploy(-0.1,self.Cargo) -end -end -end -end -function Fsm:onafterLanded(TaskUnit,Task,From,Event,To,Action) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -if Action=="Pickup"then -if self.Cargo:IsAlive()then -if self.Cargo:IsInReportRadius(TaskUnit:GetPointVec2())then -if TaskUnit:InAir()then -self:__Land(-0.1,Action) -else -self:__SelectAction(-0.1) -end -else -self:__RouteToPickup(-0.1,self.Cargo) -end -end -else -if TaskUnit:IsAlive()then -if TaskUnit:IsInZone(self.DeployZone)then -if TaskUnit:InAir()then -self:__Land(-10,Action) -else -self:__SelectAction(-0.1) -end -else -self:__RouteToDeploy(-0.1,self.Cargo) -end -end -end -end -function Fsm:onafterPrepareBoarding(TaskUnit,Task,From,Event,To,Cargo) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -if Cargo and Cargo:IsAlive()then -self:__Board(-0.1,Cargo) -end -end -function Fsm:onafterBoard(TaskUnit,Task,From,Event,To,Cargo) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) -function Cargo:OnEnterLoaded(From,Event,To,TaskUnit,TaskProcess) -self:F({From,Event,To,TaskUnit,TaskProcess}) -TaskProcess:__Boarded(0.1,self) -end -if Cargo:IsAlive()then -if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then -if TaskUnit:InAir()then -else -Cargo:MessageToGroup("Boarding ...",TaskUnit:GetGroup()) -if not Cargo:IsBoarding()then -Cargo:Board(TaskUnit,nil,self) -end -end -else -end -end -end -function Fsm:onafterBoarded(TaskUnit,Task,From,Event,To,Cargo) -local TaskUnitName=TaskUnit:GetName() -self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()}) -Cargo:MessageToGroup("Boarded cargo "..Cargo:GetName(),TaskUnit:GetGroup()) -self:__Load(-0.1,Cargo) -end -function Fsm:onafterLoad(TaskUnit,Task,From,Event,To,Cargo) -local TaskUnitName=TaskUnit:GetName() -self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()}) -if not Cargo:IsLoaded()then -Cargo:Load(TaskUnit) -end -Cargo:MessageToGroup("Loaded cargo "..Cargo:GetName(),TaskUnit:GetGroup()) -TaskUnit:AddCargo(Cargo) -Task:CargoPickedUp(TaskUnit,Cargo) -self:SelectAction(-1) -end -function Fsm:onafterPrepareUnBoarding(TaskUnit,Task,From,Event,To,Cargo) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID(),From,Event,To,Cargo}) -self.Cargo=Cargo -self.DeployZone=nil -if Cargo:IsAlive()then -for DeployZoneName,DeployZone in pairs(Task.DeployZones)do -if Cargo:IsInZone(DeployZone)then -self.DeployZone=DeployZone -break -end -end -self:__UnBoard(-0.1,Cargo,self.DeployZone) -end -end -function Fsm:onafterUnBoard(TaskUnit,Task,From,Event,To,Cargo,DeployZone) -self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID(),From,Event,To,Cargo,DeployZone}) -function self.Cargo:OnEnterUnLoaded(From,Event,To,DeployZone,TaskProcess) -self:F({From,Event,To,DeployZone,TaskProcess}) -TaskProcess:__UnBoarded(-0.1) -end -if self.Cargo:IsAlive()then -self.Cargo:MessageToGroup("UnBoarding ...",TaskUnit:GetGroup()) -if DeployZone then -self.Cargo:UnBoard(DeployZone:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self) -else -self.Cargo:UnBoard(TaskUnit:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self) -end -end -end -function Fsm:onafterUnBoarded(TaskUnit,Task) -local TaskUnitName=TaskUnit:GetName() -self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()}) -self.Cargo:MessageToGroup("UnBoarded cargo "..self.Cargo:GetName(),TaskUnit:GetGroup()) -self:Unload(self.Cargo) -end -function Fsm:onafterUnload(TaskUnit,Task,From,Event,To,Cargo,DeployZone) -local TaskUnitName=TaskUnit:GetName() -self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()}) -if not Cargo:IsUnLoaded()then -if DeployZone then -Cargo:UnLoad(DeployZone:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self) -else -Cargo:UnLoad(TaskUnit:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self) -end -end -TaskUnit:RemoveCargo(Cargo) -Cargo:MessageToGroup("Unloaded cargo "..Cargo:GetName(),TaskUnit:GetGroup()) -self:Planned() -self:__SelectAction(1) -end -return self -end -function TASK_CARGO:SetCargoLimit(CargoLimit) -self.CargoLimit=CargoLimit -return self -end -function TASK_CARGO:SetSmokeColor(SmokeColor) -if SmokeColor==nil then -self.SmokeColor=SMOKECOLOR.Red -elseif type(SmokeColor)=="number"then -self:F2(SmokeColor) -if SmokeColor>0 and SmokeColor<=5 then -self.SmokeColor=SMOKECOLOR.SmokeColor -end -end -end -function TASK_CARGO:GetSmokeColor() -return self.SmokeColor -end -function TASK_CARGO:GetPlannedMenuText() -return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )" -end -function TASK_CARGO:GetCargoSet() -return self.SetCargo -end -function TASK_CARGO:GetDeployZones() -return self.DeployZones -end -function TASK_CARGO:SetCargoPickup(Cargo,TaskUnit) -self:F({Cargo,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local MenuTime=self:InitTaskControlMenu(TaskUnit) -local MenuControl=self:GetTaskControlMenu(TaskUnit) -local ActRouteCargo=ProcessUnit:GetProcess("RoutingToPickup","RouteToPickupPoint") -ActRouteCargo:Reset() -ActRouteCargo:SetCoordinate(Cargo:GetCoordinate()) -ActRouteCargo:SetRange(Cargo:GetLoadRadius()) -ActRouteCargo:SetMenuCancel(TaskUnit:GetGroup(),"Cancel Routing to Cargo "..Cargo:GetName(),MenuControl,MenuTime,"Cargo") -ActRouteCargo:Start() -return self -end -function TASK_CARGO:SetDeployZone(DeployZone,TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local MenuTime=self:InitTaskControlMenu(TaskUnit) -local MenuControl=self:GetTaskControlMenu(TaskUnit) -local ActRouteDeployZone=ProcessUnit:GetProcess("RoutingToDeploy","RouteToDeployZone") -ActRouteDeployZone:Reset() -ActRouteDeployZone:SetZone(DeployZone) -ActRouteDeployZone:SetMenuCancel(TaskUnit:GetGroup(),"Cancel Routing to Deploy Zone"..DeployZone:GetName(),MenuControl,MenuTime,"Cargo") -ActRouteDeployZone:Start() -return self -end -function TASK_CARGO:AddDeployZone(DeployZone,TaskUnit) -self.DeployZones[DeployZone:GetName()]=DeployZone -return self -end -function TASK_CARGO:RemoveDeployZone(DeployZone,TaskUnit) -self.DeployZones[DeployZone:GetName()]=nil -return self -end -function TASK_CARGO:SetDeployZones(DeployZones,TaskUnit) -for DeployZoneID,DeployZone in pairs(DeployZones or{})do -self.DeployZones[DeployZone:GetName()]=DeployZone -end -return self -end -function TASK_CARGO:GetTargetZone(TaskUnit) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") -return ActRouteTarget:GetZone() -end -function TASK_CARGO:SetScoreOnProgress(Text,Score,TaskUnit) -self:F({Text,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScoreProcess("Engaging","Account","Account",Text,Score) -return self -end -function TASK_CARGO:SetScoreOnSuccess(Text,Score,TaskUnit) -self:F({Text,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Success",Text,Score) -return self -end -function TASK_CARGO:SetScoreOnFail(Text,Penalty,TaskUnit) -self:F({Text,Score,TaskUnit}) -local ProcessUnit=self:GetUnitProcess(TaskUnit) -ProcessUnit:AddScore("Failed",Text,Penalty) -return self -end -function TASK_CARGO:SetGoalTotal() -self.GoalTotal=self.SetCargo:Count() -end -function TASK_CARGO:GetGoalTotal() -return self.GoalTotal -end -function TASK_CARGO:UpdateTaskInfo() -if self:IsStatePlanned()or self:IsStateAssigned()then -self.TaskInfo:AddTaskName(0,"MSOD") -self.TaskInfo:AddCargoSet(self.SetCargo,10,"SOD",true) -local Coordinates={} -for CargoName,Cargo in pairs(self.SetCargo:GetSet())do -local Cargo=Cargo -if not Cargo:IsLoaded()then -Coordinates[#Coordinates+1]=Cargo:GetCoordinate() -end -end -self.TaskInfo:AddCoordinates(Coordinates,1,"M") -end -end -function TASK_CARGO:ReportOrder(ReportGroup) -return 0 -end -function TASK_CARGO:GetAutoAssignPriority(AutoAssignMethod,TaskGroup) -if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then -return math.random(1,9) -elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then -return 0 -elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then -return 1 -end -return 0 -end -end -do -TASK_CARGO_TRANSPORT={ -ClassName="TASK_CARGO_TRANSPORT", -} -function TASK_CARGO_TRANSPORT:New(Mission,SetGroup,TaskName,SetCargo,TaskBriefing) -local self=BASE:Inherit(self,TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,"Transport",TaskBriefing)) -self:F() -Mission:AddTask(self) -local Fsm=self:GetUnitProcess() -local CargoReport=REPORT:New("Transport Cargo. The following cargo needs to be transported including initial positions:") -SetCargo:ForEachCargo( -function(Cargo) -local CargoType=Cargo:GetType() -local CargoName=Cargo:GetName() -local CargoCoordinate=Cargo:GetCoordinate() -CargoReport:Add(string.format('- "%s" (%s) at %s',CargoName,CargoType,CargoCoordinate:ToStringMGRS())) -end -) -self:SetBriefing( -TaskBriefing or -CargoReport:Text() -) -return self -end -function TASK_CARGO_TRANSPORT:ReportOrder(ReportGroup) -return 0 -end -function TASK_CARGO_TRANSPORT:IsAllCargoTransported() -local CargoSet=self:GetCargoSet() -local Set=CargoSet:GetSet() -local DeployZones=self:GetDeployZones() -local CargoDeployed=true -for CargoID,CargoData in pairs(Set)do -local Cargo=CargoData -self:F({Cargo=Cargo:GetName(),CargoDeployed=Cargo:IsDeployed()}) -if Cargo:IsDeployed()then -else -CargoDeployed=false -end -end -self:F({CargoDeployed=CargoDeployed}) -return CargoDeployed -end -function TASK_CARGO_TRANSPORT:onafterGoal(TaskUnit,From,Event,To) -local CargoSet=self.CargoSet -if self:IsAllCargoTransported()then -self:Success() -end -self:__Goal(-10) -end -end -do -TASK_CARGO_CSAR={ -ClassName="TASK_CARGO_CSAR", -} -function TASK_CARGO_CSAR:New(Mission,SetGroup,TaskName,SetCargo,TaskBriefing) -local self=BASE:Inherit(self,TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,"CSAR",TaskBriefing)) -self:F() -Mission:AddTask(self) -self:AddTransition("*","CargoPickedUp","*") -self:AddTransition("*","CargoDeployed","*") -self:F({CargoDeployed=self.CargoDeployed~=nil and"true"or"false"}) -local Fsm=self:GetUnitProcess() -local CargoReport=REPORT:New("Rescue a downed pilot from the following position:") -SetCargo:ForEachCargo( -function(Cargo) -local CargoType=Cargo:GetType() -local CargoName=Cargo:GetName() -local CargoCoordinate=Cargo:GetCoordinate() -CargoReport:Add(string.format('- "%s" (%s) at %s',CargoName,CargoType,CargoCoordinate:ToStringMGRS())) -end -) -self:SetBriefing( -TaskBriefing or -CargoReport:Text() -) -return self -end -function TASK_CARGO_CSAR:ReportOrder(ReportGroup) -return 0 -end -function TASK_CARGO_CSAR:IsAllCargoTransported() -local CargoSet=self:GetCargoSet() -local Set=CargoSet:GetSet() -local DeployZones=self:GetDeployZones() -local CargoDeployed=true -for CargoID,CargoData in pairs(Set)do -local Cargo=CargoData -self:F({Cargo=Cargo:GetName(),CargoDeployed=Cargo:IsDeployed()}) -if Cargo:IsDeployed()then -else -CargoDeployed=false -end -end -self:F({CargoDeployed=CargoDeployed}) -return CargoDeployed -end -function TASK_CARGO_CSAR:onafterGoal(TaskUnit,From,Event,To) -local CargoSet=self.CargoSet -if self:IsAllCargoTransported()then -self:Success() -end -self:__Goal(-10) -end -end -do -TASK_CARGO_DISPATCHER={ -ClassName="TASK_CARGO_DISPATCHER", -Mission=nil, -Tasks={}, -CSAR={}, -CSARSpawned=0, -Transport={}, -TransportCount=0, -} -function TASK_CARGO_DISPATCHER:New(Mission,SetGroup) -local self=BASE:Inherit(self,TASK_MANAGER:New(SetGroup)) -self.Mission=Mission -self:AddTransition("Started","Assign","Started") -self:AddTransition("Started","CargoPickedUp","Started") -self:AddTransition("Started","CargoDeployed","Started") -self:SetCSARRadius() -self:__StartTasks(5) -self.MaxCSAR=nil -self.CountCSAR=0 -self:HandleEvent(EVENTS.Ejection) -return self -end -function TASK_CARGO_DISPATCHER:SetCSARZones(SetZonesCSAR) -self.SetZonesCSAR=SetZonesCSAR -end -function TASK_CARGO_DISPATCHER:SetMaxCSAR(MaxCSAR) -self.MaxCSAR=MaxCSAR -end -function TASK_CARGO_DISPATCHER:OnEventEjection(EventData) -self:F({EventData=EventData}) -if self.CSARTasks==true then -local CSARCoordinate=EventData.IniUnit:GetCoordinate() -local CSARCoalition=EventData.IniUnit:GetCoalition() -local CSARCountry=EventData.IniUnit:GetCountry() -local CSARHeading=EventData.IniUnit:GetHeading() -if CSARCoalition==self.Mission:GetCommandCenter():GetCoalition()then -if not self.SetZonesCSAR or(self.SetZonesCSAR and self.SetZonesCSAR:IsCoordinateInZone(CSARCoordinate))then -if not self.MaxCSAR or(self.MaxCSAR and self.CountCSAR/Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)") -end -BASE.ServerName="Unknown" -if lfs and loadfile then -local serverfile=lfs.writedir()..'Config/serverSettings.lua' -if UTILS.FileExists(serverfile)then -loadfile(serverfile)() -if cfg and cfg.name then -BASE.ServerName=cfg.name -end -end -BASE.ServerName=BASE.ServerName or"Unknown" -BASE:I("Server Name: "..tostring(BASE.ServerName)) -end -BASE:TraceOnOff(false) -env.info('*** MOOSE INCLUDE END *** ') diff --git a/Core/SpawnStatic/SPS - 010 - Simple Spawning/SPS - 010 - Simple Spawning.lua b/Core/SpawnStatic/SPS - 010 - Simple Spawning/SPS - 010 - Simple Spawning.lua deleted file mode 100644 index 44b5fdaf5f..0000000000 --- a/Core/SpawnStatic/SPS - 010 - Simple Spawning/SPS - 010 - Simple Spawning.lua +++ /dev/null @@ -1,31 +0,0 @@ ---- Name: SPS-100 - Simple Spawning --- Author: FlightControl --- Date Created: 09 Apr 2017 --- --- # Situation: --- --- At Gudauta spawn a static. --- --- # Test cases: --- --- 1. Observe that the static is spawned. - - -local ZonePosition = ZONE:New( "Position" ) - -local SpawnBuilding = SPAWNSTATIC:NewFromStatic( "Building", country.id.GERMANY ) -local SpawnBarrack = SPAWNSTATIC:NewFromStatic( "Barrack", country.id.GERMANY ) - -local ZonePointVec2 = ZonePosition:GetPointVec2() - -local Building = SpawnBuilding:SpawnFromZone( ZonePosition, 0 ) - -for Heading = 0, 360,60 do - local Radial = Heading * ( math.pi*2 ) / 360 - local x = ZonePointVec2:GetLat() + math.cos( Radial ) * 150 - local y = ZonePointVec2:GetLon() + math.sin( Radial ) * 150 - SpawnBarrack:SpawnFromPointVec2( POINT_VEC2:New( x, y ), Heading + 90 ) -end - - - diff --git a/Core/SpawnStatic/SPS - 010 - Simple Spawning/SPS - 010 - Simple Spawning.miz b/Core/SpawnStatic/SPS - 010 - Simple Spawning/SPS - 010 - Simple Spawning.miz deleted file mode 100644 index 69828c118a1eb8646207b9be0b874c86877112e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8220162 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

X|+QVL4lT` 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

r|PlhYVPL6{_p!D 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%0Dus&UXH6u(1!+~ed5RY*Nw6wbO!IxxLq5h=V0m=Y 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

TFt z(GeKl_Ni}p`?b_TjposfhAN$zSkMUlxw3mVh{dx*3u{1J0Qk>q0L%`43BYsq+hE9b zNQy2=bI1;*h3@r;Bwr$U>JU1xO%l!lqyHe2*`yu$z`F{U@T9%oA|=YWr8g! zV1pn+>zQ#cqLUk9QcbS)gENlnO_{Z(K}W3MY}dd+Fm&QurUfad2MbnjaJoC4 z`Jn@~fY5=ClWE*aecs2bwD4Ml6>h@}>7lH17M!NRA8*#1z(wQieVx605!;JarvP}~ zfemH8eM?{3yjK-MV`q?89Z#H4TZ$FXMSQ5ZcO&3?6EB{P-o>sS2Y?Q&V~xT(n5*cZ z0Rq$kJkZ%b|5C(b-`}XIL_=G!J{Hf<-R2mG0JAf>u5sdcqCUd4^xCN&`@)luCR5n_ zg(APH#hUcHoP0pJ|EX8%aa>-F6#;sGVdmsIGiH*ug?G@6hVpfbbM6Bwj^uT_L$|A~ z1W{ou{SkJP^N%%pH8*RbPuNsb;q}1>gbSbtXvq*8VHd;XA5w}|YtA?e>#9}wDwlUQ z2VAI0e^T^bp-#&p`l=fnlKIz`6Jmh%Ft2o`3BPfg)$a6KMnie((JLlSu2b4}ySod2 z7ey>J0%;ySf8K?^WdW>I;po-wq2GXx-NALMQ%-xmcevBa*-UDp`)U`yc;Y(ZrhM4f z``$x3RikX^WToOw_P#0Rz`w{sjgn&ZM<1M+L<-NSKNg| z!a{MaO9$oY1a7%?2yd`{-(LH+n7J;891NJK5j8Dr(I38@Z?NF|ynd6|D^im=fQ9yi zj68)?1bR4um)r5H%vqzE7ve2XHCgwDR}N54dfRqCrQ_58+P1JAx{B+lL%+m8^{WtR z26s*d=eHxAbes+FJ~*nFsJXad<=fd1iHak|TesdhZK8}tXe&HSw{w*R=}j(%j5~*M zbg{&sUl-`gT2c*PDwWw>NshPHMgU7mcMso3Li^>}|27nV~Jb zdlbf;Zfh2L(!$`~HbJ(x_!vyR0i9{f`TyDb*5$TxW8MFL3Pvf{p=M}C7hhGTchM4M zYmaV`=<(cADq5m#W@u7dr0j8Wl4sh_aGvaZeFGY6Veuk)X0mrBRq==mK%>!UbT=A} zhEj2F2TIAEf?0>ny&Vg}+RBdDDmG9`oI!(}%V2N~r^9{zd2gpakn7t6&9s0pwAxwY zOc4zE3GxG1&m1#pOWP15X=`E<`4aqZHH`G|r6t^B6l^b~9MFq#L0zaX!!qD57mj?93}B%i^s0&ht$Q%q z1FGQWk1rtj_ZI?R2y8Pq2N*&A)I2zVXTL)rEw56Fw6u##g-w+&imHFE-~d(2L3K;x zg+nU6v{S$;7A%KTRWRSum=*6T74Rz-FNbA}C0rUWMC9qcrUFK>U|C$`7FB(J{Am~W zJ3`Q(vTeL(Rjj}Zkj&EgR$3aQD6xvTE_h*{n1Nil&>L2=rAiJLTodjVawS-u#Nm0} z046=93r2LUZ?cQMJyH+Je-LE|HzC$f{3)_k#4}xf#BJRx>`yrmdvg88HAH3?Zfmvv z*;@INY~>@gR{Rs0!2h%obQ-TmcRS8ioNF{-Jap}}#?5OyDulYzuxius7TXO)@R_Yz zPPCsE<0HMC3L+A_S~#RjG7RUu3voe3q^0?lw3~3*(7rFhXVn3$XW*^I*@wZXL2nQ> zUhchk*#JW~_K#lkTr%C%0ZZze{(bk6)wfm2u4dedfiw< zN*s44B{Uf=`iJ;%!?)3C;zCRfm2W2Hn7$UxA(h-Wj;G#uPFwsh%4!Kz{t9qXEO2WH zTxNk5|4Xe2GVcU-x081NvA8;r`C%3r7Uar)xVhF%kUaUzql{=9CsN}%MLIpM-tx}mGc$#x{!VtH*s51%Jlq_ z(kmgrm(u3&VofT8W3*711-P{i@DHC1kPHxXLx04a+8{xTAyYdjt!;blmW2ZahsH11 z<)u}_TY2V{9n%0jNXl8tz{Kf3qoRAY{s+PzS-F8`rQA9^8<%j(d`;%uZi(bUxG zAGAfB%-Tj;yx)7%ABxyZD9Lw-sXKKR7O-+reoO$}!Ob4A%zs_}L+HqM-m!=i=Q{l3 ztMNxlw|InT0i=eLx^>sYb67ewYg#7`Xxgvli=(3#`yG+fa6~Yhmiy(;KowV7rK+Fj z@<5svfP}98%q;ikSDjaKe=tMBKNDkhDOMdf_>M-y&#wp5_vy7npvDS@6w>ivIG*xy zh=>vdbjC%3?Bc2?+=_ZeIA~kd%g-WsS zP>V1t)ROD0h%Xi}u_Bcc#70B^VH}SL^tj7|HlHs!v#{t?qRdUQDlRuMne8bu7 zGqa|$P+%jioU)StSY#(HBg)B~CY3)#3Nce^Aq_WETiMIxZl*?w&63_`v075`cI<{t zOh|~hG`nfzGP`ZA&1a~{OGQbdV406*v%}1qUZVJ62C3pd7Fj{dA}b`7i>#o9krk5K z$_fYVlhc>&eU&yzjS?#)rL$Nesdzh9;C1o}TO&?FLYZ19QH!dK2}?d?+q3Tr2{Q(H zqFMRs)>J|A)P|IOR=6Zw9e*`!N}snB>{m#q$j2UxyltNfNfsA04D$crugNviLH#0s8=#m?hpP~ zuGA7kls*zrBz6P0Y4FyUJq`hyGsG4GLGvyKnrRul^~PPXw}EIBi_0fPfgg-*zaUR2 z$a#FB)t2H7ZL|Hqjz4r@JoUHWkusQD@(KA^i`j%mI4WAbL;tquAF|l*%TG?(j!$*^ zjyuWz>tG~bAF9wjL>0Kw=msf+SxDR-^nTGH!rMjZi>M<9@Pj7zHN5nIhx)RjfFmy! zS3>wM;@trb2!^KJBj-sH4CvoLjSWBU+0?*BC*Yw_8O*))MR6&nX8MBoNOoe3Y$04q znV_0**x`;CavCqFq!5ZyNE}6eN4l}YH1a8sDWV-E3n5kjiV)LEut=A>1mG~@-Cfhp zVaSVnH$yZQCt0nBs4o7gL)T2tY1u-9M=H|o(>juno*NnY?-u{N5ClE#7jMETC!oUR z4k&1u6%7yPPc8nJ1cF%#E=84OP*ktmJ0vcjs!LSE5~Xd3N-RSlivYW~GwDip)V_C7 zp#qsXjx;0vUtJ$u67_7;w+&GOWrT_M5>I}?@=(7%GTFeq#^Ygf8L1}2;aq%(+n;E} z$8=mvGDs$Q`k1Ql>JiS1NTN1!U(Xfld)j0(GS_*nl3-fB11UljC?*z*hH`XVbbK!mavJNILhguNyU_f;<>0V3rw&G zCI`K8Dn49OwzPc8?V1tY6%e<}@gh9AIUtK^l}O4cOoWz;OOoB|(a#6H(b0(gvv?_P z0vnJ;Ck()!KlDbYxQB#P*)Xub>2$@1O^$Y{IG0EmAHAs*w6=;7!*SlOh`!# zuuK~!Gt`tXuD52|qRIC;*m^9N936`nN1JCa?eK_mFe`4tUI+tdnY~p%K)Ie)6hANiriH1WLNYm_n0%y zq@b#|vo@@qkH~={XF(xDp5Jd2WTLj9?VmQP)%l=}cVKh|g+EqZbUe=i*BFmh<7SIL z%aIiY&67+Z53g2?B4KMrgucUt+u`v-e)^+!_X4-Z>WoK(sz+&q^T|6B~1CbUpZ<%YcU_=r+&cz$qHl%PW&W{ri)WhgV6Mo+nA|DQrC z@HI+04g{-ZS#dl<3VhBbinFBMtMP|M=OfZAH@ahySF@(`l&%gEW2u2q`-oX26GD%S zLLy;G^ef58YU5Gu1CG?`K8BeUnrc}l)}1y<-rM9eL6HxdidScUt72g^<3k%XnZ+|K zk;n*1L%gx2%xJT?Vd} z5Cc79Z_|lgTp;|7F#4!Sz_*LS`szJHM~rmluR2~6IP9J_pW`8#_sVIRVoE+zU7BW> zt*+Na&T8C&tgf(q)tk(~qC+=RvB|6kAQt(49~R&%_(+9C&+O}4)%{zU5grWKHPx9l z&J(_ALVyK>CKeucTtwZgR(B7{+}VI&zB-dK#sG=PXoc1{8i+en34i8|eO zkL{w@$8~V5Ae+lUW@UJVhxaZexgbsXrl@RgoJ9DxkJ7>P^EZb_-BWPB)D0;X)C|_n z5lK!X!tGj9m~_3BK27K?7$J&%D$-PFX{FqN=S*Oe0r>h@@TXHe{;i3(16rM5F8Nlq zWD^jIA6()h{5Lp(#yjE+gNARTBEk`>bT$7e*oNg&0tKFFl2vFgI!Rv3A={g$n__AP z`kW5G=1Jhfad$Epr&N7-R>6ST)^wKVlp!9Qw%!POrLW_nTjjXeKgA{(m$R?f%Z)l$ z{d@dGDEYHDk&S-T#G489a?IW4`r8`PWKQ9IHW=#kmS6npW!> zN#ca*DX(3Y-CXK7L~(EqwRH_T*wWEs+lu%aNtb?3fK(D#Z_FH&>Yo{e7B>A~7==Cc z1)_Grfpk;<9^O?TSweqv`oh-Ti&gsSs5PZ(n!XRk_yM-#|6;+xc*KV;Px?Fg5z^z)9qsub#D8-kglOL@}lrL7f!#xVh2008cyouI8K;w1!otdh1)f{Y-K`c0n319J9zo3?K&F zfy*UMWpK?TxLLS+`N(fn4_bq8+g?7m&g8!{4`1$HG*>a5TbNnALPN(aq&fHxoDsjp zLw*!^x1$QsL@|P@@)8ShNb^mbb4d#X#)0xHIMi1mY>M zbx8l^QC8bdTW6=|y^#)q6AqceW^;?LTSBTkC|b8GOD`>hFNeJOlAK+On$9|7x`P)gcKV&vtad;Tsqo8E zeh%?K1wPUv4~&IPhS-<8{EfDNdaffpCsp0Xm?nfL1sWe$*rmKka=0{!0hb`T&L9#G zAP{~U;R*bverLfbMAt8^GyOy7f&^1bKQ-xxeuwuEk1VLUq$!`NILTp2A6UI|ct9`< z!JN`3Kj{}LQTfJ>WzXWdb4`33dQ?G;S4r*I9UmUl37XZSXYkZKnnZGmwpt;@N-|xQc zJjZ`HX(B8Q{;!;Aid53NP%`|k-g9(UxBkZ?$qy?vLAUlco)r0+RsfHY=+=eWkUhU> zc~0rQ3x*ZdPh?;H6BeDBMoSIgt#P&RT+@goetKoR^rJDq|8H5?v2+sY zrjj@?iKtK3M5;+izs9SPTAy@xQy))*X?9{FJEzB~*waxzBR*Si@P80cE$g1YwWP>> z+K^8h&>(e^y(SU46PP@|MK$ z(+g@V(phA&=T%%My6$_16^|dY?X6Ynmr|h9@t9Wv;=JOjQ29el+c23I0?|Uaru7*;4ZT#&u%0lYfnkI@cp3 zHCC9q*CTf0nY(%`Z;dPdu#Dp#KR8~ML$)bRp)iOZx>;&L*%nsPK#VrNgk~O8Nr@*% zbl58rMJrsy`AyUxWEUk}o=tFy*fPOZ?={|plc}DZ<~@BQ|7E6=Og7X;EYHL%-C$t0 zDByBIvUhu7TIoQ0Zue1H(4a&kj)!fZiA2thv(GMXkBns-)(r-eC9^e>u^5hMfdq*2 z8qjf~^necFqF>=l420FQRti@b8VjBZpSrT6<50vFO?r&T^Q5q8cYig0hg2dH%+nAD znB3q*-RCfEK>Glhvf_mtaGDSg3@&KmKXTjx=3BveqovKG#CYL+-OY&A2(GCNs_MxL zDYKfCQqQmBvj!3@OK2E3Cr=}94DguPSeV8XEz1I{ZSku@Xz&i@B6#qX6-V?>@nEMX z`_b{&Qac`SuG<2dWXn`@UB0tea5@rH8?OJi0~5+j;kSE!|`0d_J-e4C9xzJs^~e8XU@7~wcYq3 z%0{>v)L41)WTmmbCZVQeHA6II7(r6E$yAeUq=U80Oj`_s&3;JS#wIDljWF=ua8NGN zr<5t7-cm+GcwxZdZh6NSL*2)~Ov;Q@MD(T&N$i7XivGz21+%LW>lxyo^3>6Px8cSk zPogHWrN8LBi%RH7iyeblA@EOXgz!p3gaZ9%d>L0tZrwzemEq30y_~};eg_e;=OEgM z+F({MyY=XE!QI0?SI|$_mIMQ^07!@`ONuKQPtxG)k;2$wVePx$k`7ZA6(}lVw#mLH zUbXQXM`_$+8O0c0aMG}_2jp*MpP8)8S;r&PWpG%l1DKq)ukqbu&UrOWrXqS72Q@rW zk`+7j)82j|Uo8Q(x{^?nTReN9GWo3WG3l2(V=C}>oJp(;F{N>0Qc=k%|B{#hCn9>Jh;|f`P^zftG&8MbY}Oeja5^=xX!)Ab(}*PQA1S< zoMl+&a^ylr79Rv00E^W>Z6X=VM=_frIqM%AAJIT%pmRnB4w*JST5@NSO$^zV98EDy zsEA5NB=Q1LGmhjM7F5)3aX}RqWULcm7VG$a)j|}Gz=O;yySpnvvM8Pz-0>)L-lzo8 zcG;DNMLE9}OaZ$Y;)yn*PB?U7YY5TzQUfcT|4Me~LA5@{W*8 znmq<$90pJ%#agZZ#OcTww#e7nIA}z8{cyV zX0kmA1tfObAg|@<{r5Cg} z)}(K~h^B98@4Xd^j-wrIV0q%fFd9% zQLU~@98c+yF6nut3%IMX+s0WY!u7B#hG<4Q1WJiC2Zckfi1Ra=nnk9^NVMdtm1fm5 zlR=XMYSf9M2v*6H!YfY&?y@$Rf{p7c)W5rv}Xmbwzi@7ajcVHF0gL~^iJw%g!>87 ziMkstOrS9j75B{~*A2;pQp`=Q9PPMhUu4xI2<^1mFB^<}C z`?G4mry?(dwz3`J=~1>@Tlbzc*Eb(%?$V^M@HA^{!=67XmNPD`jT|2IYUFzBzGe|k z8dA{)ioQn1a}p0J8L4!WN;foZS*h)PD&5qKrb+1#W*|fnPeK`Tm>}7=CoQ>G!3O*^ zg+KiRM`g7rmW+Sa9{jAyBJ4}^J94ew6R)?+tGHq5pj<_|8H*S}Mk;*%A>W>2d5RU$ zzSK{--u?ioj~J_RXIhp;gv%J4igrJcha!~eL)uCp{#AQ*snoH;D&ipgnFkl^?ve-{0i~X!(nsuX#tLSkGpXX7=RYd!0(u8^$ z+7!#Oh-9dfwkwbauQ~nP8BMP~8-eIUjlUR;VwDQ+fp?9W=ee($U>6H22o)mon+`~x zjn*xA-&c*~QnBa2Xjuh9If6xWwY1!}cQrJUb?EOJcbJfjt5lFI@D+EZHPK6nGxVZ@ zj+HA`kkI6tj9hExKxX=`ARhy%BwsA8z-XhMrK9 z>s6qhAg&d2025kry*w#>5#R)&&65=iE3iQhalgcTeGiPET2>nJ-?d|$o6%JrX>+5_zztW>BS;t+0Xt5bd` z0kouo0w%{oEuf*;>8Cj(4z)b~`Z10<3?MD3YQueI+wiE%mJYJSE4RGHO)upK`csL( zU+0iO>qG+Z2N^!5OsS$DJ?8u=GgkjSU!Q?{z&ho=_AcVJhB_ktTZbgy=ZwiJ#X-rJ z;;8Ivx#P^Zq_P%Nv`A;rwMf|(A<}q3Nk=C?ba+}aXMCZSM?*i#7)#p4mTIVlrt>Og zh@j0gRPZG!8PXC(IlCR_J>DT1(SOq|9=X65-}XJsZCTlux$Tl z)%c=3klm**!UOkntWLAJ(Rxr-wAsq8BH;l`Wu8#&*W-bg{i}G%dO1;moSVE;NicQu zpQUM>|Ba2u&Gp<`NLT>!B-f4AiA=B&p5#dI38J84kt3PTJEY0k$TDMKfc2cSi{@)R zT2M*1!U4h1D3xu_Xm`J&OqpPEIi9v2<_LFlE^(k`PNOMJAqA}!vXX!2UBr5@#3FL6 z^GiDRx7tUT0Uz;Ur5y~BYRQW%QiIX?;0}zkc+}g;vGC2R0)?3b`bL%b3 zI%YV^daGEmyz~1)>n(eP`7gZQ{%u&2@BDwNVfmj>KDgfS>*%*R>uqz^YFbLa<=h~P z>bJFu#^TLcuiBIUUK_c`|6XtZlh+&G^Lx|3x=IyXT!dLW6fZFGlR8I~oKOSoo_~JP zdxLaYlL$L|%Gy789V%A8cO9yf*&LP){aUP7LrVxK^jZnyAf z7qQrE@9yrM9JIT8-*%c7*@%1=57o#`s_s*=yKd(ssi+dqSA2f5om5f#)K}SVpPcM< zPP(s74!cJ$JMEpl!xym){1_^t(l)0Fjuz6C)Op!H+289Bq7fX@bL3FiM;3<8&hF94 zNoSkxU4HLdJW+R+2fowU{$_js=(MxrYd*@>d~wq0oce0_^;u>Qu=Cw zx4ZxSA#Muorw~mp`kF6~_IKLd_Th`pe$xA6RXl0kS9rb6P5S*zC4C|11T)m$_m!Vy zIOK56*T{i8T=z9{GkCa>)KF#Q@jl#4YGjZ;yr0y_!FZ6=$T0EY!=y%Lk`EsxH8S&i z_&BMNqu7(VH99ZKt22jh=k@M$4=gu9N5{#8Z)8Swe6)Ai^>8*b10!}g+J1@k5NqNk z^UNyCtu0MR+VqoZ^?)H=Nj~8k_Z zQB|Ykqm%Awr<2N*$Jx;mM8nL}CW{Rv#fg=$iOkIFX*;|2Gx8C)5x2kDeYNkF>4WB@ z9LR3_qzi$Y!}B4Mcq3N46bzl$$S1weBjj5NI`DtLI+hXtrn)#ch3%W_;tV^?)5SSP zr`yt=b8}02JFkm#s%U$LF2?H5i1@rNeyowke$DT6O1e0=7<0N9i=kvN<#q9s3|*^q z@skYHmq!I%Y$v0BB$c}Oai;fF7w3fd(z-a;OR9@=HBw!ikz0p}F3wsFi7w7rQmHO} zkfCrv7iW~(VXBLBR>RG7@p{v|)_yh%y4Zr`eU*|f&aH%;E?#Tqw0~LN!_d!|09G%r zi#IcC%vx0G;taLZCK*-yD68``s(8J*mPM&b73VmN^V4~OmaCLk#TKmMCoZFk?=Pu} zH)vGzRWY*uJ^%c#rG|mhyj?L>Deh1g+7+W=?MtX&zzN%7{7pB2n$M5+3!7VGL95}? zFnV5jXy$3Cms-YaP4jSv3Zn12NBix=WHb3#nLboJI_T_ob`yi#bgk9d=^VqJ10ZZU z50eo@!DDqr%VER)?f0+S-*%AXBo1Uf+(t@kpB^7|A2}Jl-YjA%P^n&0O+|}A+ijmF z<0w*h_vqC=sr85v<<%4oY%pH#y?E)%%}-(Fd)OrV@*?5)j}Bju<$Ht;f2B6nEzjxSqb?m3n zEa?ExvXUm1)I_mLr$t^Uur*};^S%AOAN)qL%($i1hTUF)w_kVmx3S#Y&yU(CsZdy3 zR1x6CtbdWc7^wHFDuLbI+wH-C-xpK>1r98DMz9tOQ?2`*qWp&?Q-t)J%744VX{;aR zx|jd3ObS@WdQJjCousn6B>xLga`NB&z=qM%^1nd%O8H;FrLgABF=J??^1rBL(v4No zI4C$c)r%A$r}Dp8n#zAY7z=G3_1Bu4#nM#%7Y3Yeph_c3<$n>YME)1sR>}V|vmY1b zf067t`Cqnis^!1!lZ{{@D|M9rWvoi_zra$(a=S)to#gd{ISi~;Mn4pdjtc$27|-7* z==S9K=YJg?@mn09-BwR5d2VLY>NkT~U!9rIzGC6r%(&H8neD6FXy$w)MPHpQ&;)MY zSC<>k9ABNT4i7x%tBcDvty%Kb*(&hWMBk(amswN3-few#c_d4|x*UoXzPdcbygKnC z$*Yv4M!K!Y98MI@&2lxe>LuwVt74KGSv8Z?NVXa|ZY=ofa+qdSPSQ)pSC?B+hv~T) ztd%cK4eF7c zVM$+<*M6a1q+-CI_E9PLCJ)Iw(~^!TlTZJmJKje|n1wo&29&Xh;-}rfU}PmdbHBPb zOS&9?=7UUg24AB>yi^7HX+9ywr?Nx~4aja5GY#@yT^x80rZ%;HV|aS6#clOosfpJY?GD z`5s>azykezKfeAyNEH5_YkQ)mY(qrF2G4h}kDTtHKyj@V#-Z@>>Qgd+2jv0qR>pU5 zJpRLoCK05YE8o*=ycLTCt+^Q~t{tjMXdj=ffN zx^$nuM;QI39;4gFkMa~xwhV@Yzu{#Ev?!{JB6)r7Fg%#RrOxQs%6CIKCl~2J>N(H_ z1Bux6aLq$LTw4ULn+-a=Bq-|%l=Y>dY%nf(&MI2EhZ{>n*?dH}Y$i}PZwTdn0_Fbl zP@WJj2|pe##D)oVHyO&q1eb?P<05nPD1q|mhEN_SP#!N0Mds*90tHX7%wH}EU3f&> z`+ITk(B3Epfqqwc-iFKUHz>t(HQZoRM-&aIcW z!nyV0Ryem_-U{c|3tZvcdWkEXTQ72jbHioMzB5}v0_u2-SL~Nx-?aY zm!g)2{;=%wpa*JG*o*E(6CqyMu@lcc2i8ZsuR2BWKzvMZ>@JP{V=~HKv`^dJ9bDZy zDG>ZcZ_=Lb3~+mHS*jzrD?mm}**8T#XUN^&=;y^Yc8)IVh)D&`s?MB47YfbYLNog7 z0m{8LjO;7C_=&+RO{6C@lyLl3f;^-*yB6G`x`7}&CQs?fb`c^D@Rr7r$(=jaD+1pZ z3Dd<3SKYBbkhNrQbUs)T2Xs7G?rUjdNcRaY=)VN?JuY4k`dtuxNxZRTqM0n8JeIl; z2QGY*_U6NqJy9eedWt8O#U99&BCq2u4H<6`7QOL$H=*1Kzag|_4-}>d-0g6*2vj!Q zi)~yJZht@&iASkLxVyv8@K;?O_WR^vUIb^fkf$C;*2e8%%bR0=kD}N)y~5Onv*{h) z)>x!#H!wPi#lZM9w+#0BYFT(h{?Y^|$H2v2R0h(1rnjsHQ0(JQz~tra_W}XZt#<1n&dsk22h6lropElr^im3_oWh^pQ&6Z~$@(z+0N3Ci0+8=H@(sOniB zhLTsA7GbRNlTb6>gL1lU0|rwifs)J5voX~ttQJAl^-l31nchPsJ(Uat_CJm?$C_2f=u{HRxQ3GU?(YovW?h2@PWmoK3YWGgvMAgC4+*P3@~|!3R** zoBOT@6!ITHS!q(xRYhDEo*8cI3zhA0yM8(BeePfF^rk&~RPa6fyjroKiPPxMm;F{t zj|iI9UN`nl@toZO`b#uAdAWCZ)VWEc-6b30(%kF)&WS!?_-;HQf9f*h>)wB`t*s~G zja{a7`+mn-!;gSGP*+Q}{`T>6dwP*woy#=dUBBNZu3BPR(8T)djeD)X|AU_S6qb-A zRruQEFrFvP$9MP#qHajLC6Ai-`4a`{v>rB}$PbF( zfkfoAROo!l!!C4AcW}4Pz`(EYzQM*$|D9vx#`>cs-XIIO&93{<>N?bCw?J}yfo>z4 zS6z{@4lwZ9Es*V8I$*lYtELRV7(i6@j!CLr;o&>`NG2e+ktg0~8og2s;x3?uQ_ zz%UX#PDMf&WdVtG;2Duv-$bT*H)=v+9Z7fmHz;jw;wXW!a2>@#Kr#vl9bpNr?~(&} zg}|<0ax}OG?@vr@>+{_$`%de2xpjXNQ6W^bzVEHCef?nVPn&CB1uU!xp3wEj;qyIM z+UrQzBW$n)nj}oqK4ESKu%4jWaWzqQ`tLSKdQ80N&{jrN8%E2Z{f0yheb=?~uh-XB zzuH{;ldKM&{1&1HA3yoZDr`0%;jM$)P&IVAVL&}it+axu&a^m2DsDcaNT`c9MlNBU z;a?;->|YjC6Ma%u_rKf*xt$du3h}qI18!%v+|J(W4X4Ty;643|-bd9xCzsd@#tOZ2 z)vS&ai$*S1Ff(AVW{-PQdiwSh+}AwbZ_@?*162mYsXp?w+wV`i;{(aejW^n$N4aY) z{s!+_Pf&h2sc$@pI<~r>_R03%p>zU#yoFN%cHO|v?w20RR@F+2EIUT@ee|mPJ9WOZ zchdd-)$vUFuH)UbV5d~m@HrC3og&;|0a)^^ap|prO}4T~WA@I?a-B~<3YtPn$v{9g zj$vl&JmRm^rpU9=;~8AEY12*IDqD`3!+bacmgmUS>bn76JTr@M+4GZ5`x|6+Ir;wI zgqkUGkjr)dd#2VxgaUIdJc%X>7G)rEiCCLT@c{|h`5%OC`+29W;vCa3YppIwSzc;J zr3EMpZ6QyxL(CLJ4G>D^lEqX>O2Kjt(lC)LbE@CTm*f0Z@Ubye&h#PD&~#@kUtFUb z-GPFx(2)V@WQ-N{?=LCvgsP(I5~5VW-@uld_`5s{bLsFJf-JK(7a7*(qo~QemaoE+ z-b6Qi7T_%bf3s2T?j2^8*h0f%o)+paFs#joOAgC{Uu1GO*YP}O#pHrGw!#F$<9@;k(iZ-gR-qYGtQmB0ZAIHX5 zxWW%HqP2Ph;Zp-hRW_*#4K!AJkJu9&68)3jqT_Q4tN*NJf1zbyc?Evno4^OW&JFN> z3Pyq-Ac`w~wq9&x80j2HU1o8I57~JC%-X6*D|OI0gsuES02jbuR5UXGf2~2U^W|rk zZ=U}7x_=Go5)M3pJN-A;@1zTHp&ef#idSlJ1wnk$SZU+_@GkSfWJQ=R-@sU+v7&s0 z+7&e~WdqV!(Va+jO*&7)#5Go4%OBM>Cy3%7aaD@vr@v6wMoTkkQ#F&VwZA!RebxVf z=Xfu4>vh7vghabn;}4FAe>VJunv9S(7TU`L2*O-KEUd-dtA3wP-|Et=*&8S+yR!O* zgNtSp=V};a6T?BsWqsvfj7RwJ-hiyuyOY{@#8=g7`138Wswo9)|3#cY_olZ&l-k{k znobusiFDw17uThv-b4=rG5Go_E9jXMtb(U`pvmqexwbBa0XqsjW2SMV&MBvwL=D&t-@0vUdp)>y2NSZWbh zP5QXXdx6-16nvCDh7zQVOhDJj{{;WjZ=*}~4(snrn&Q?v$t5Givt*zO!%?^SXZ`l$ zADLwK^DlPqRVxlxGAx!YU|JLs?tVjYCGDe*ye0M#hwt>286_{L6R|+~zOt z4X5^=7YLxn1@jKJ@Vj@aP?(QI}?BNNC?mJxFC|FDz~f z3k7aISRDf`sf>7I_LeRX6C>8hDa>@UNGudY7aJ8dqp{wAXSJFwQh@RB57tosvT@Oe z>BDouf3bY#zf58=R;Ne=b8$WFAC1oYI3Gh3QK@GMaQ@aP^Z?Y9=CMHLIR1t?sb^Go z+{aJ7E3%}+6W&{AH13z!DYzrVIC7#uc~P96_eL5x^8@0KB;%Yn`_AhE}*py9V zCnr#?yUq2t2x5b55KojGdUVH!2X(@La?hIP$9A~E;t;S~jQvZ!mrlm=wr0 z7!jGQWN*a8=H;#AvvIryIYt-5&tgfkm1Z}Z=$5N@TfXr z#hh$-GBXQe{HJxzKh6!F7dR|p0unE13lc^c)Ke=6qC*_Iq8yqd(D%ns?Jq6f@UvRV z7IV$8aG8C{DH`@YXZ&ZJp#UUK5!Yx*MWxfvqw%MaNSkQ4?Xpoo`@t`DIe3yJPV^#&0~cArM36&WhoV6X-#)Q$cGAA&ov47iZ@ zeS!lP0O;#_#DKtPI-k*dN{OjxXiJ#2WdM;G-~3PhC(&5QnI`Ua*TffC}iM4~9~ z#qYu-!Lc-$yzdXkLRyxd1-xmMGvjDS%JU z5=7P1AMHJw6xp*g?#uvZsvcn@I8d_dZfMJ>4#F-Px$lG>dD?>4!hvWq{bg$_J&`}5 z&`s+B#35&l6ROG^L9Fta&0WT|bn)zdK*F;-^mu39vxqYNw zu$FGD^rxca47yYDKvA5!vo4xuxEhqzI|)g6b_2&o1XjHrQ)TnT>SF)ojJ(%voPHm^ z@Zh{zq?)`)cn=x20d#96>*=Jk0X7Q(tyeV`E%vw>Z_t0s)2M_$F3zO+=tzQb8O2IF zC3DukxX`w;G-EAmD$JHxrfy0bxlz*9QII*AHg(&x`FVRf=a3Sj%|=KGn;^r2i8oO` zDv6v9ceFa|Ll)1%=Snf%PN*OwfyiieoZepPoSYn;Y{6c=9$v7g6B!|OD#&lEkVN+X z!OaoT2~KaD_416X$ksszY3(t-2bao>^(9qJFR@HZ_J*pkxPT;QQb-@P0-r`5UA-Ah z5ki!SDSC9@`iM2Z`S3oi`OSyR_w|iJ)$UafhPEBiego-@I3=#hXNRIQ&ObWax3uqg zdmin}%(mqH#ku=9(JUzYu-Ga~leG{l&j6c*LHW25i zguyxKV0wMg&mA#K)30D+h3c1U)DMq&NL#1US^E0=YJ>4vomJ}rS6ZbTByB~YZkQNm zpDszqe!9e-KTn>m*S*h|lnVyi0ZM&X&N`LrUNo1FaHTfBba$HBh@;n~w*#13NYT9yp(WjV1JgFalg|AQ>PvMo3~B4&rGwjRR(3}& z37lNL>rZR!Jz#$a?ZFfe#=G3DGnf-Q{ex_xQWMiQGR&B_ckJoh4Rv=l&%4J%FIfmO zJe^NrEEUA$rdO)6_LR?K<_{}yfdewJej4U$9Ied?Sy)NisP`fWb3dC}B`51mP({u3 zyXY_zg^KCrn*FhEe{4{!5^Iyj<$B|CLk3FG9=0G&*6RwR8L-ibxChS@^C=0{1ah|E z4FB7U>jC@Y;ZK(4B<5hgDAR`H=6m;^@ksk-@W^%XN;&rbS1^2%xx^0(pUF#PFZ4+Zk^L6n`12zOt0TFQE}bn+wC z$@dT-RiT!l`xgKss{P*j=7TCYPIq?Z>&~FV%QS;l?@$_Hak}D&buZYe>NO|plx$w1 z16}>>4a4#ycWiH1CxXdAnW`a-^KxSrr7%PR^);^2lVfI{ts}J+yc_-kro&Rh-}}?v zUX}*5Zx6Qrj5YM?@&b7?V1*=$2)4p+yojQ~l(*b4MY2!|I&s#ltu;4R#n2&5YKHWx zvps}Mf{9kP2iGg?LCCsa!z5IxsU{&iE7%+q>A8S8xbphAcDzFdJDrlgIHRiYq>4INKLsSzi`dnMQZA0_(iJj zw$~qTFH-Szb0Hd`nj~fvizzTcI6P1Zhp3p=$_o4Q=`?=IIUD(K!K1~ z7s;T2JS+>@dME<4PjLw_3sDe>8FInNH{TBW!~AJ9({wnV;3x^64LVJ#>wlZu*ml1g zUwv-EB-}Z{BQtFN$s~j$Ro-kh?TGC^4??(py0E>XC0o}^M?o;)Z*^AOvEIuxy}8*^8r zOp0n&Fw6hprEUtKO49{{%{yz09-j6IE|I+0svVBusyWBO)Ya#ju4gzIgfcZIO|AE_ zHyG0C4nMcU_7k>9IDhr1(S`^E)=wf1!0s&;AqMNkSW=LQ`&tfQH{jMirST;C#Qw!O@I_ zd8ZU&YeTD>^AqQs96?7KIPBB0Z#DPR9dxq5)UG4R51mD^Jw(=y!A)~*DJ0qjHTMp` z-NR+5=ldN;B*vw>&CAZoLFaVw`gkg`{hBkuEQVA2pmVafy+nimM9T2}qa`rfZXZ6! z8E{*KU8M6YZwk_)Fmd4mgG*tf1+PLp6-3@1Av~#bc0AcsKfFVXqf7VjAY^_xPtWsUg)~oo|c}Zs_#WpTAAT95|c0W}&Tj+#OO1xoVcsmMYZHL9zwFeq`O zCV1#~4znfvx9BW|-2Qq3V7nKRsyGcTZL9>!#ahb&qDmV(#MwQH>`t^5iu{?BvCPM{ zbi;ybruq+#PifPmWVu-T(?@xTc&oeVB1{UK7OT#I(0U)IQNzBIt^?vUTFW&&`cy9L zbjMtwPSgLj)}T;nW&@B?=gf4A41H>pr68ul#TOkpmE7rCy}*Aoq4%sgvm1%BAOgA{ zaPc=(HiQE=f+V&UVkIpGL zcPFb%O8F$~+ydMDH!k5rFxmbTiLIjmouT7S+b=p>hy71=!Ui!Ujg_6w^H(ocaQs2h z2YYX2$~ib8bAgmv*#URW23aA;iXmTuvxR%fFJ73kpv>rE^Fq}sq(dgK=tFXZZ5)2A zqWt}FZ(}fe`HlxtTu$`+7{LIO>nrCJAWJl?&AgSS=n`_);hQT>qy(6F8U$;oLOzlp z;Qt=g(%Z(%8wk}*1%xsjHO{+HI5@?x@{OL;}OZ$x;rFB*fJ4GyT2G9L+ttWaQZpsX4(ng znw|Ek=GzbfkvA{ggt3N>|1dVLm~QCvX1M@J<}}KS-15nc1XH_-rqkG&zTqu;%Fy4J)^9S)B<*FkE8W7#35a>s-h z(@UsrO`}o7>t@+x79WHVE;^i#r7anR7EAnRdf{8up4H&cm7oouF*Sujh3cdJC!sJC zk<5vB`QKpl1`Yd@4GTWuCi!HI*}AbGPXz<%grUyx%o#h`IsKMqdSIPJonc~DIR`kF z!8eFVr&HXE%)N{A zBcjO$t3ou`=vg+b=*tf(9*hKJE=6V!Syb$T8LYW@8Nk}$V;Yc-53Mu&0||95THL0I zzf|=>k^l{UZ!!S~5uXJTnG`MlmsXAm!o`+JBD!#)i8F1;e7?D!a)i`71im7lioa>B zydBnq8OOpccCB44ozv8+CX!d%$I34=7Iqe6Tf4ot2-2Y?PI_7pSIv>}$~O#Apf8IG zwxEEA4wIYb{=RZz&F}(+r%6ds#Yi+mWnGsDLkK|@BZzgK;<7CL-J*Y-p9cmerj|>B z70{m|5Sa}&-m3-SdUiCts25-o7AlpoP>E ze2O;2(^d^O1XHmlRs{?VR!vHRuo4?1T8nCAxoLlk#5?RR#Sj%!5CgZrG<5_%CyYQ; zL4{{6`j;CZ!jW>sjRGVdzWzj|uidz_UyD5l29t3ybR!t?UM>}olI{TWI6gnWz8v7j zGh;`ZoEH$t^gi4!dEdLd?2rCXI|P2MyBCdQ=#&nKMkglMkM!qXK)i^1*zgN8FnHn$ zB9yU1tWD#mIgek$Jn*Pyh_n-4%OquqxH=$mPfQUnN->9Z21-5%_0aKehCf@lK0rVyit3Fs~!{ET8>8~J4Y9`S;W1C z!Ii`y00%2=@fSf_YDlzOl(1nPo&(yHvIR`)a&^AJ4Gca@>hw}QSB_Dpf}YhMi9?&~ zNdLM-=u0$!xjg!s>8EB_z$myXSw!>)rB)civ#cq_P|W!0*rf8--Ux9(T0^AQjO+KW z2ou@-;$|{JDONa>1Ua!Zss&QDHA)mj{ONR+O(L^_YH>n&f`k--s9xixq{T&Iw*Eme zp%H#Z3TZp76G?>+QUne?zY}2CA78t!LsbcKwcd#d40T5LYiryX@o&WY3ue_4o9#?a zv-Mw;#7?$sX0r*;*h#>lki-UA%3{d?;`nrMmc0`5&9a}>Z80Srl~5KqRuQd@O?H4O z;q+U~31S;k zx4%QTZn_z>hTvW?xi0&pOfAPfDc*W>QU%Z(w1RD=;A2h-%J5`RuyE<8 zO&5N){-_OXO-t7rfyKg-oH6a~!@>E_Gy~>@mKrvf>XNp8^r!Y?XGBFD^}E49^1Ucu zBKMS6nPq2llU&%7{4^p34=Mf?aS_gRwrWfUuvh)=#h;j~4CT9%2DV~aG|AlJB( z2@Rc<5WHilkLdM>YL{8;2gXe`wWKRLj{&vb;&prcsonutS$}Pu!QYOYVNO3Q&**oT ztV6%FtjUaCe;UHBFFH&$hWCj!UkN#%}+(9yz^E^Vz-ni$P% z#??fG>?QeQ%+18W1W@uR3txFoWJK$aVZ^VbGAm`ixKO>G$ z>A}8&QGXeHPWl17OQd{H!w=u&al(LUoC~X=0%3Ih;Z6Tat_iUF>qt@pAIf*lx?_!0 zh?}ep;E51*31-kKQQTCtbLJhM`=+tK-g41;F=$z~1Nzw{9!0e1C!Qevdw%~cREeDI zdQwJVaa3UbUB<4ai>jv<}*A3a=x@|V!AFi}=fxh+{+uW0(Id&ne_ z*A_Aa;Yo8>tpI|$47sTPNUE;A$3k}ygf2lR{L1oF2b2+~5Wj^)KuQBea$?X$l=6_g zb4jEu^r1Gx2Hm+uXVX=i=z{r+aj!Mf|AJg(7s|@8G9fLN|B+vcdKAn|DfpHnB?OR5 z&qUA}+`>n%#`Lw`5b3opKG*ucKx}d1Hv)u{AIkJU`9n$WgVeTl)8JM8DJ?2&`osPw z9hgn9sS2d4z!R+WwJg#>8)@|l#qH6>AsB=Ck2r*VDX}#lV8~A@^)tw0)!aPP5iOn< z@L|Mcg3^w18@bd@Y9ChWSujj&C;i3WP3p+?>ELn^;mDZ{=?`DX1#uj{h%A=L;N8fl z8M5)v9VaS-mU2j(BiL{>ZQ)&GJVvy&i_nwjpAXStAR9Htt;#ZLz95xb|OZ7^_7vdi4n)#=OboK8m9kk6(Q?gt&kO% zR)-?o)Kgzd1>AgfxYNPII-MQg3c^=YGCEKu6A~ulWzEaAAVqu{I|sxYjqY>N#B@bC z2^_#t!4k!NhN>%n{4~DIeC&PfkKew{6pY_y0FOV7%T?cuhd6SlE9pf%-qRJq@@H(Z zaWSy`eFz724or7A8OJb?F2xEa^VroXA%o3Pp}Y5Djg2_>?bF~&4nf_wxG!-ZbwVea znEGmO$Ieb&CDy~ya@|7s?fLuq3RE!$zQU1$uC}TfJ8zx0)(On@WsO)F`IJV}9@1pi zep28+tu$*dakTlFHo54mHpK;AtP)DE+s=tm*ckBmXpB6KvyRVR+pSp{V)RPjSV2=DzM)&PIDsNm8A(Vp>KWqd7G2DO zUlZn%RA7HtNo-&;p}5}}6V;L3QZT#B_+=yU6`s`0*=B6uQVlKGhCf)o-3Vy-7Q51(D}7pRNTPom*|26Gw3%qZsQu46yhbe(8|HPd2{wqNTky9 z6Q~2_l>j~=!Vo&;t-dgiTZfp}np{jq|H)731m2B>0PLVShYRqmtRPZgX4>vKB7~ZAePCP zSEYLP)L%kG&q|)mvvPAK4+BfQQzaHl%v+O2ta02o#M6muQq{|YC`A`6SbPK*dya2LQOnGs@go~HKxz7`K+Fu2m>crN1Ybx4PYs(UMQ7_Z zTEkj1Iy+MvCrA~Z0qGJHp}6X(Ou0+_8hg|aANu5-=eTQXWpSP*yp!s`(g0`>LQ@fm zyTR1_Grt?jg2W!g;^Rj)D5Qxg0I6i+gvZVj&^;P3-&IM?N&*lL4rd2qMzs^M1XmcL zXp+KG%a26`j0}l7a=GTffVt6Vs(RPMuP_>2*GrDZ&x}V?#C&n_l=b$!Fm4gR`tX3v ztqCBJ0O6oScZcTdo^w!u;5l#BThy*u55S?ZV z@v^SOlHSpXD?eE+G9mtonMh+XG%g5xHF0MN5R(l8jYpsWWCZfP>76{XDD9q-;&fVZ z?JZ~#yO~7?1nHv_Y2*XFM<|_&inDhl=v^~4^bl*z@ea;{56`p4=KMq}x=+5ypkxG! zIg%H~Dh&!`X5d(}!HBmfT#m)*T2Xx=fo)b zh+HWuzW-8EyC^&T6oq%oj1X2G(ZXjc`UAXzpO@K=ENI1nn+M~W5y4JS3S)0j70F&p zMg{xIiBIRquw_Kbe})S{mcT3QI%$u-%ZpBrP{AU+yLWM>dz7I9SO1UR)>p_auu_VP zD7}USC^9~GWT~_jZky*VqFQ#`S8@#`D?78Qy%a0RqhvJ;91Fj4>xT(KRGqttRC;*S{0;O32EF1=j zk!3Iyw}!M@y`#jz3XU1CdR#RkseE{FYTe~5c{ z%=)`?7NQf&6#v2TcyLG=6hwnSG=~5?B~p<8F8D)>lF+*dhi?9qmm;mDoGC-LQC0mv zXEr_3_aZG!>n`5H9%EMhbF`;uQL^ z&RXkBQ$q+L@{o>46JnTr40MR~972m2AJR}9)5hM>>>&8cqeW*5cg@FVEckda%g<;R zrq5}~++|;`3TLu%%yZUIfg*Awig}%WEhRfm=$dwvG@jSr+==;f;PnKq@I$`!X@#`P zs|fZp9kcC>Z32$k^swtp=H?J{(9pjr_9s$rtiFN*x)GLF4C{LM_YOMC;c@GXO@mh&)OPFb0DEK?$tFFM#_AtmGW40@2S?$ODChkT5et+2cNaATnDlkL4j53zl9zBi)x$!-Jk=vB7>^XPhd zE1)}jC*AK~9eb!dgRAN1SC==&^ZBd&{nO6(9S`$4Ue}!TKlg78bPrq9z4pF`sWC`5 z2KoG?)BXm*iYJ;1O4Pt}#Hjv^_hha<-xzQQi8sGz=pJ1L>|kbfT5P%;4}*m}H|sI9 zT&~0R^G-WiG3__~-p!W_1ZJn3%!7*mt9@Ma-N6&mlqk|s&$PthVAP-9e5zi91Nx<6eb}|k`!s2zIsJ?C>_&DUpV_Af`+|(hv_tCeVv+urxTfv7URVz|$ zv3Ia^3x3uj4!$|JkM_y3eLyHIIgb6MvD$m>$^U`_yWE^EM}K5XJlX5)3PeYnOkf*} zq|$|T0DGKMTz>dyZ=|BUR_h#%&*a8H7cEl0C< z49^5I;#~DF$5&H?x`r*a8yi*TG@ZeL%B^RMwQxo73e|z+7tc^9-~$xLq8v zG%RP>E!!A;#*;{wx7eAcAh_Q21mu}vuG;l(H_#U1}IKZO& z`=k$(7F=za)C+*@7BYI4o`gX3TyXEr1TVlF#MP${H7|>hId}8z^Y~gx04TM=2NDcD z+_)u%p`9=GmNn((Km0WMetb>t?O(L~6Cb+2NBDE?O}{@P5tBra6A;z@Gve_Ht;UM( z3K6r306Rp?=UIWbx{e;q*SQU)8ljopg-!m#pv*{S3FZ2hLv1WqbB@}7%dUI)ODN)c zZiR| z(_omc(hr!)HPQDUkYkC{64aT%OzO10#1VN)=TxEaVJXQ0 zTbYyVOVW%W%bU-3%c*vb8;hjzxLX=>yl0=uzE02d<_5?DCb|0{1d@ejVuov>i|sKC zUOHs0um00bcl^p~ZN{979d46sB&As%qbaiT87R61Q<|jNgK#!`X$(97diHyFeU03V zSO&=KMTQYCE|Igz{%KwJPlt9(SawjZO{Gp#`_F`46=ax5Y->Xqnqo?$fbA|-P zKpPE}k+I$n@#FLHFR1&ksF_4c6;%^)EUBnEE)r4#AE%mXa__N*?Ac01RD@`jNhXTs z)fF^uNoOG;5>Gg_T0(!#eq2MCLD1pDLSEbQd-9{hlh4^*y~tW6p-_l$)2yzsZA~ph zq78*u^vPsyvSvp}g?fiL)kd(OqI8YUA%|Sdoob3nl;7S&4<;SUi5&~>A~#J$s>%0l z=P6^qFP%~>(qxJpA^k6~L;Y#geGlIeT_mM!w|GFTy}!5fg*L96^1M84V0pGmfi+2> zNhFH8n)7X33`dBtk4>w3@~tf3WVu%1Aims;oGr(^NE6?x6MmTY*sQ6mGq2kUY%FkT zo&WV3vu+EU0?Ed(ooRzfMwdY6H7Y(H?;w{(65j04C>)Dxe7P)5dG*#+CTT;0*XLnU zd;Gh`4~?t$aRhAHO<*+;@svm6jI{23N8W}>}qIlsbu zOz~DNM#fJ$;kDAx zxvCW8U{t36xjbQ5{cPaBpBfwqH=V#=Ip((m$$3h95hmdN=@FNR*^Xbd32pA}?03Xt ziK}(cM~S3MoL74@{)K(XO+2>p8Q0m>{rU+{h?Cc~cSRjp z?f*zTttJMs;608-#*VL)s98um);Vuz=G#=ip_#8={f1_~e!Kb&&3uCyZ)oNlq;F_? z9gyrKUR7&>YRQgmTTyeEI^g6P!n{xiuaQcl&XSv6tP>>8SOjH}hzF!W_(vvJQY>%e9GD63CVqmM07V}Z91p1_I(s+G3*S4UE0qNCD6R*5!l4`z z@G0No$Uf~YUJ9E5)Cq)2s5bTRRA-hlB-JiQ98;`3`&R6{J6Y3QBI;ix!Gz6Q`CCj| zxrAUb>(W)c?$$Pw@iUt#|^; zB-sMpr&r_8b+QXxmb67Aeiz|iNV-)m>Zej6(&fv^_%1@xj&^r{FX6su_rH@=SM+W# z&I2G>lLfI`W+z!;vPVf?GhPXh77*Y+Dk+ZRzDIU+ zI5%QT|BegR{Aa$BtoTw@2KJ@&%5*&b8IDt+O0LvYp(Kt^zt2jJrwS^|X_qo=Q!53? zLdC>d{30rbDYam_0Kq{ojJD{%h@ar=q{S@N_BkjFxp!lGE%)Cl^K|>FH<=N|fYZy# zuM)+uebPR?O%wyula;dH*e-Y$wOHpe4Pn#a7M#XYI4#Dtyag1UZ&;m37)+bBc2y8T zV%afrw--?g=GFpgyM1!{a?9d1)1Zdb;`>r^fO)QekqUsE^=CJpN@o;m)ij#<9BI{|Ay{Ol2>9e~{UuqIPI902(L(w_p&I)aHgBrq(XcHYd^vf9( zo&W4?clVBdzwP3p{r^s@mFaW))v91_y;>E{0>;FbTCIIz&EIRay0DIF-5)UwtH*~E z6gEEd7weZb5m@Y;&E8nruA@pk$Q^mY{I?enC!GUa1feLr-)qyc1P1>W5*K*=>$evY zSDeJ*cT(vK35kf||2iRYCKl*^|LrXCfS18t9MP9EAaK{?`_9RJXSe%%%7`Vg_?_g0 zVc8aedV8~?1n|}dMhW6?Xle|>!FGU@?hay@@2|U0m~+;1AcxMKOl)WB_0fto)a+mA z4YQJRKg(YD7uKYry(y*2k-sE+(TcTLvCrRq7>ru?UUm2OPn(Aa-LX89SAVz~E8yYm zQaL=H3ipS30DhpCw@Uyv0)YL&hrv|Pp!et%tbRpPI9QX96&OzH$g&~x`3&Xx)U(7Qc$dRWX|tC zW`Lypr72hs$F>yt*81V-uyc0O-r0L~x`nvq`ih>j*Vfl+hX*x^z0~JH^w6Iig5UPf zH;|28y0G(?WiF9*HNGpp@6diTp=g@)ju1sok(1Kh8nc$qHowAMF?Helz%6}c!$%|K z&YW>Etk%22o9h%Ok9(rE>9|Hn)yCJ;aM4p}Ky;#bB{s-*iUgthq2A~v;-ot3X)oiGfi@5bW`6*%RnPSKntEr499oHV$D(Tm zUeH~1qd-+@npUP+xadA@J<%(hX%9g|k}&XDgx!mk^|3$b;x_5#?~ z{yKg`(wm*R+&lf#gfvMW)gpZ$(Ys_#mO;IyM}BGu@PN|cVzl#JnfA@n@aX0x$pGnf%Xpg5k;gH8i*euR$W1nqhy8*qpty&3Rhz@567QK&3>lAU2*1# z3<@&c@xUW4FUEK0$@eot3eU`m1a5dT9Y!Ckrkpyk1~N%g3qyaL~6#$1KvJX%~K z69Hxh|{B|&OjA@b2pad`1j=4CNs`+!a%+xre^dApoX5l0H*{5`V&1tW}qN#~-Nsfsps zm%o2-U)@y_G3(rT$Zj^Jx5(DWdSZM?3+Sc?6dV!P(q4#DDOB#1o~=Jtb`(2Tja&+9 z-^P6;T#?=#U!BN1B|fWFcp=_lFZQqdK;1{m#R%ygSR;dTJZ}2A8DA2W3FMVhvNa@~#(HV_uN+^E&-=0y9Kg^Y^oGrqlG(6= z2r$#6z>E|5p;{H5S?iXqL{daj~@F^qI)YSnG9|~k0rTkxDX2FUc z32qgvZY722WjBR!p)b6EUMTR=;7HFbf z@tEl!9Iw^x)m~C48-++WYNzc}Tn}y3UX#6a`l$~G$-~G0@4x?`5~r#ml`pu)2}ENh z)sS;mSuQV8%w%b9gj%o#MDW+*mZ!XC_yl`<41?fee3e-_e7BY~ZRB;)u%#BVNOZqB z7|A2JZdTYivo|>$PxWSR0L>~@TacrT^W3Eu3lY@Nd_ZJ zI1dpA5G$4$YPKOYc*}SwUd3l)ZNI|mpQmjOPGn&EJwORF*nP7xi_=IfR-Ve^T;5t0 zresOYnB{o}P$C~Q{g{A^A}i8G&oN(u-81}Uii7l75OLIg68N059an>MWypyPJ2&C~Yxhn)`YXP?@O#*GL5)Mh+-Gwxkom{m@o z-wqc8y}Q34Xl6&ov3#Liapls64ot6-KX_2QOTH)!j6h*LTyro;%+FbIIgw|%A!n_^ zO#V>Yrq46ymQwVYerz2IZ^W%gv<~O(gvdBmgOAKgK3ENkKLU)YMumtlIuMRuG9s2+ z~&9fQGE248EfP6R*qIa}|7!E|yKomhIs9^Ah8j4SED96-)lQ)s3aX`FMo za$X88eTNltS@2%U*iW9nz=FnK+xxt8q~+;GqbNy@0?}!8%{@8lJOsEb9RrepSBr zB149%T}W<9ZNw)aimt?U_tu*Tqt#iNZv*~2N7v|16(u*C6oVx^{@qiq<=A&ATY3M8 zvfPM4rhA#C+V6CZ+b3NlggxotRj1BL*ng7IEn5y*Q^*(G9qR)sUO}!9@7aks!tIge z9tQ#{_G_Vd1j9)z;3q)fUibkZjrdU;*z)&2&|RD>q-dW^X{V$0{G%n* z^F92-=DTpn$Nn%Eko0XI#e?Z*+$q}(?*P1b*LZQ+cyXax1Iv%!TF;P7Oz8`H%o@(l z_^Wvex0~+|7+?Bwx*rh(8s)ZxFF(NuXaEl6cdhUI&mUSp_@4+Dj!F(c)YI>9)?0=S zVtd^7IND_3Gf3g;y*TX{>>*wuoD9d8{wdKi;Qd$DU>|8luYIOruB?m3b#a1=COKT_ zdy35Q09PS$RkRt3(FWx0E>Dw`R?Ix`k3CD2lo&K~p7`9%7H^Cw}=Jm3xw)miGV>CXO zz~dk}XQgH@tW`F0NOdU`*$}4^udwZ61f1raFIKrlS+3GnTyTzqj?{mVmu%DCn_(Xz zgA<&D`L!@b9^c7(ToY?VU?zCV@n8ZRJR|+jJ7y#ROeqa;jGKUbw2<2+MQob8!_VFK zcuniwd%nZrWmL;pcgEgoSevy^95TNn`6#|p)bpW8qbOb|I z1)Zy_@s+OtU7;1;1?5tz1P!E>NM||l_(`-tr%ziac=wsCc(+nW?dFd;0xwgOdN`tY zph@+tK5gA^%HF_1)mTpr^pu69(bcN&khqo?y=z1-RpuArQa1f|dw;+Cvhy7O$+(An zf0c`Vss2umUUW{lH*rIdLuW(h-t{JQYkp@7be>3(_*p(mxVa$OJCNOAU;sShy(|Nu zNpYrhEJ2iH$8rm-w~JVBFON0e7onRu=IXFGY+V`&k$A?Jl5gAlI7IC1bdTtfyNpD=IS=h) zox1_(*jVJb&+l;*b0udgUxHiqD+R;;Ou-PjrN}J*+I+J9U@o8RpL#Ya$t>nLck_RF z7Q!>c&-eECe(0q9qn>0p*UAE1;8~-5=^b)Il-je_iOLKyarQ)4u=g;A6nv5cJXr%W zmHJ5+J|98TG2anVz!&VShBM3>u%}!|D{o>F;7pw)ZB*e-6?H=CvdKHcp#@|Q%A;2+ zw9&MjDN8NV*T_AF1oD~X>8P9C<+yUA2(LgF7evd>nSEK$O>Q!%#k@WIEo3 z>=dVN>mx~J?jaCOYh8HJD@gtLZo*F-zGFwRJ}HG6cozbVE$fNhs6ITX6QQd(NDUqg z>D@&W*7viu=Eeq(#4*Ctd;FEvXn*t(JLtFLmDsf8*KN&Qg6d zc=t}093B3YSQl(tqow(b{?Ya5I@Yy`^(g(eT$v$^5^VM{MfaI|3%ZJyt_H@UqVtfg z{Pfde%!oEv0PI$)(Th{ragdlHr?(d~_B)Chd}$Q$OGFLI1z(ahg^D~t7B+=({7Hu~ zhn@T|ULEHL0q!xRO^(>_xq~QhPXYUsg8+1C4nmtO()9iR650RnGx?8Un)-%_lc-zf zB>d4TAjsS#Iu*}@ATj>iX>aU&^>)pgjyj@=U}_ju$Cp!jgTz`kk4C4L$j(4IH7p)9 zz9&^vdbsa#8@kxSLepBGJY-qoP-$3RxKPXb4j+jc_>WKnf3)y_*?VFdg$ft_|IgmL zcg1xaYvcdxQ*4u^6|goVK$2r~G+JxKO}yr223d+7zpQ~7U|zutXNHSVlJlAJ8S=?~ z&r?->*|)g>wv+SAIkA|%cXxGlb#--Bb#?XmRjZhvEA+ia^~nk!w%Rw{W9(1iFiTFo z7B7}0ipwd|A*wYTf{&78gw$jXxN5;BrWCz5YDi+gt$iS~-^xa|-61|IcG0-yXJKaX z_k_UEeSE|9%2}$qmw463>eoBHI(t_91_7u0jEVsuLP`1Od|)@=(LLlUjqI!C&wKGSeWs-qASF-1!*r%y37 z>qw4;Xl#P1oTIzoD){U|rWm+PX^car(UR@u$$96NH%n`PggFCnx8l_q5)&E4gnUiTQz%o#@lOz=aBpKvr>gOlg}IwyaE>LZ zzKGCu|I8G+i?tjnyKY!`XyBO1?eJ#=E0glPLr?sV%-_5pI^V?w*fz8Hr-oIrkW28< zJD<}9BBgEajWEbPNI)RT^`#9ymion$zZHi+YAThMez)F75+tm2VP`2?=_CzQZgJFE z7#2pSeprx3lpRJV5wwcdaq!u?;#k(BRZk3jym~FAYwL~X!94~70F>{P{V?b7CJi8e z2a4-t0~&7>jpo+whllh`rAnq}1o=Dk3^R$#jwP-!5hJgPfg5&ef%7FaWmpmbXk+qslN<7=2(SsV51XIAT(e67jW8{0ZOpxML)<7?QaNe*Ar zHlxXW+dpYr-d8lC#bfVXvd)xnvosn$UCMQW5+^p-J$SV%} zb%ZO7T?f|t*XLMDkycc?wVX7MTfH6?Dib@2;+-@QrdmtrkVVFJb=AVS*vCSt%^jxv ze{R4G0YSTt?d~{cZ<(-nk9h0SaCTp|hb?P>dBGLZ2~Z+N8Qq3230$IF^fE%ZxQ@qF zE@6WWXI;}Fr<#-elJVD;tXofKKAq@hSP$0 zDAEH6F;A$_`g-Fe)O(=DEad&3S8NqO(Qo}=K}<;}Kww_l#&F2Og0C9N_FA*q*jSUi z`FxWixBl`$A4mK)Pt+lYgFx5F0*O9|vLKI&rnbDievZ71~8Ju*Y-Q@ z^o1RL@&hJQ-j5V96tPv9P(l}1YaB^@r$x1wT?5q)-9Uj}poeRgJByVx$*G)=pacoo zr`o1i-+jNey0-GYu|C}T)5CCE9WrlHDEPAJLGtHiOYTsp&M-1UKFu9>7*F8#qD{%^ zk1=n_IKg!fXP%mFm}+t=pXQQaJtD$gxJ{zc{f>;Hi*qOBh`f9i=80a4_%4g^H{&V; zpf#Y(%7?&d{D}Gxz~7EVVvggx39n3b)yZKS@C{z#dI^~_THmha_VE1V4^7nE72Tz@ z3+2+83!c4$*$x*E4EhsX5T0DzB|0H%$r)|Xj->xH|-V7^7#*7&<*6cf7em#~S3VjnYnd-?q(0--y+ zQ!-i_gdS)sdIgc@M3Ny>ouuSH2UI2fuI6$x&$o;K=Fp(0beBv($d1=hsc$sRMarjO z09}_wmMn)#-=^zt=f7KsKDzBPmqCu25eB?ow|sQJ0RrTDwF8Xb#vCqc9<1%`Y&LcN zKQexQ0)d{D5@<5q)_3>!*AOINI_YC$S9?{CUtxZRA&>E%m`}bdZ3qLE?-2ZHIDjB2 zTT3{SlIyI)Eo7h3GY6nru6^hZM82#JTDOfs5L~x-kLMu|VYuHvFaAL3R-6syp+rVe#JWr|h9?)n1j>yIokYoJ8#`RxO# zjLeUSaJG5n>_W7jm-*j)H<~K5TOte6_OVlU#|iVMR0xJl$(pgnAdb31X2+s4JX(A1 zN&C3oKBjf{jk`9aU6*R}?PD+HFD^fzOH9zIxsi32w(ISA8TtHY>8YwSw~LuYOL>*! zuxNV!?NUVn=NP?xQBmN9gYdGVz)M$v!+`~>{rc9%i3rr;5>)|(_m*T+-Y-S(O<>FW z7twp$eU|qx<-M?B-A381j;gTJ@DN2OR&H)D7wb9(ktDxegd}j|e|x!9Qer7n;zdb` z7nu?-OG>?8RtHU#W6ec0W?V#p**79!uut8|Dy2zg}i@Rc>fabt#?rq!lC$L z3>m)Ubbm9O6FEjBbGc4uFsTjI!)iA#`ky)*a5MM3`m?Gt!U_aw309UK>-S};2_p&h zsF;z3m*I=fUg(0^LW(RWTVheC$rhlfan0=v8TqH}WY%8d>M5Bu`Y#(~3C?COB>AF3 z1_0H|OCS_f?7Xd@cCp&D?#?gSoziK>&vE3D*mwF#X(IJ=TAWRf8-FiVCPY-HcBE`( zzFJ*TVr2R)9|C%b2twEt6`h>)5u_ZWx0(F7&ct1qH-QATalV&(rNsQrlU16m^nEPu z)s5@tgHnhG;!fXi#U4He3(Z=496Vw4VEzA6C0th40-b0V-0C)}*C1||-8!ZAph zO}r=WQu<>KZT2iDH#$E*T|7TalOZM{`AYp^;-iTst$b;hTo`wgT17X31rH}9{FV(- zk~wh086*{?Z>b;6J<&Hoq$`1R?uWNCA|bE@d;#QKEUJESr=}yEI89tvfu`Hxb$$)Mm>*)W%QRZp3LJj1M(}C<(7clx>rSI zX`phyUM~xP=LNozMGkk}NX%CzizMB(?hX%H<=Ug#m+)Hk z+2{rJbGd1CZ+Gj@8@oG)ZyQbZ8_y70vVzBC^ZDjx5^luMAs)a%)H+uYiv^m(1~-r_ zmHGCMZ+x+p=rq5L;SKI=rFDi0lyN0!GslbdjscEyuMW6J=oRWbQ9>RsQX|4) zbzX#fyc9pYh#y|Y53j<*-{bl90O~J}>UA_|z7OMw3WvArHwStY`1=sm1D}4xNc~ah zl=b$T#~ZQq_?yh|IloIZ%_9e_a0Xe$n-3Jgv5=@RCBRh_30s=BA(AcnCWsb&9w%9} zlwUDUj=Ek*m@HW=lO^yv=z;-sb}z5Ypeqq8U|l2uF(Cq1z}1)$!lVKY&lZsy*ebz2 zFkvO3Ahea*;YI5+;*b{t5V#P=0Qu9OwB->%z+f*&qrU_p2=pTf_*l}wbB7MtWSpdB zM>CSH>9gnQZF&@OlfDkzq|f8HNlTHN>gO!GSi?5>ZT*(Naa`rqlcV~P{$5rJ_q{I5ed>38z}^1gJ#{YbnVXd<#f3_G&8u`hDm;v$l^xMIH@^+vE3x|pDkGii<~ zr)M&~Oln$MjU|FcfapAJonM}{^fW!{=^mTiii{wdA?_2ixrC5$w~J9d7>K7fU5IAe zJty=S(r)%1fwHjq<-*g+7}53W*P(1RITyWHc!+=;K~G19N1*5mypwV>qR1*_JTt}W zh1*^mOFY?_a->$~^BlCV-hxOD#!TG)Tz6ZBw{^c;w^WB#Vf@&xFS?(joeRhGEF5#g zH)EpB7Z$_zXJ=eyw_;_f&d#7Wb#u%f|BQ`e0*_X;0^nP3Sc-Cf{4+L}S#eaI_Otfq zXQ3i|pg&^^I@5zfJ1|^Qo8qHDFS=5O&8u;6p`zUb>^F}LmWm{(@TYNL7oh-8gRa5- zb}U@bO*wv~a7n=+T2 zjS|P(O%IRhmhEa7?=b@>N_GRpng8=)_m}$Naik9NwJuCS7#1Cv>bs=#ax-bT>rkCs zgZp<1^>9UR%+fe+uf5ybqyFb$u7-CbOjawD0ABJFbW`sIX{v;**zt6MdZdI4(91|! zq3 z;m0<{fe$#%w)q_hPld?VXJJ*KmR&RALGyLroh{My^c8W3Dx{GzII# zE&a-z<40~X#_`2R^NrkJVFRAY+7-!@k`I}Pc>2lHM3Tq5el=E1^y{K+j))M4zaoMoUM#P6@`3sE#job_X7ZGV4v zAIG*>EO}i|7Pz8OYoIn^u>4v0wb{S2(RyeZ{!mo>yJtmSSQvCFJMPgszem*n*JTO&xb-my5s@nQ(F>p#L`R5NwV0@yGaHBHTdJ_jTQQifoA% zvOwwl#d~*?4RXO6xo+<=sTU+TH5aVP)h+j`Jei2xHw94@pVk(DrQxpOzJ)1kAVL4M zHCCprAqhsN>IV*UPKgyVb5B`g)3V+OQH22yH<~zT)VWNVG8hi65OHkd+c`hOneJfA zjk}WwFdT1`Mp+z8MWB@+>8cpYcZPOhF`bg@70WTh0A zvWP7f!rXnm_}w?PZ@twn^3xcyDnOIM^2O?^WdV-{RW1NC{RdaBIrjU#uO5ISBupFl zKFph+M|?D$*}?PF!yuyXFTsOLDnB-e zOOqG&g?COuCn9fWkXf&T#EP8>WU-s9avfrNDc97ctzPT=ZrH_1P3lNY&|17Lag@Y6 zIF)lhI&X%Z;~7H)0)+P>ZuT@LGNL7f#wdVtQy!NqU4HfsaMd{v0GDEI*4HR_`$D>8 zXB+bPBPHff{O4lEtxO%9#KPiaY4=l62ghJI;Yf3_D5iM|8G(@Q3DO#A1|s6rj^&N6 zG$kj90&`N}r$}_%;<_M0$UW30!>=6GgEr_EhJ(Ayj^BsJ|5mb?t(_um1`_+=W@0=VVN#Bq^^JO5DX5VqN8b8r zm@$jp)kk>Qygq`-f;028jT8|FE48~ij`CRbkAg*<%Vl>!@!GoT_In@UjreHAAr-YN zx_9Spz1#1jXlH44_G6mKf1r6wU~HMF4HMkCMS!_;?gJ;i9e0Po^`Ds#IoD0@)`6=LXF2})wsL>oYkVQ+gU((Nk2ee$3s40=R+SO9wNltYmq4Wzv za(>Bi-C1>G6?`OcGfY&Sw=5r`<;G^BZFkv@wO-&X^M5uCrr0z9v4ni?Q2w-owx#Pn zqcURc!1lB3kK1^-5sZ8D>gG3@=&ux z0n4#q$fgu0mdg+)Y?~M(OB%MQB9M$Rq+lyx2{~2r?ims#p>(`^7&l!#uPeqj)9w%h zFUn-J`w}q%(J5q{ag)0|nj!8%fIf;o)*BY)j#EUY<#^Zxu!SAu<(yw!OIt4|5%gvk zHhR%I?RBoYNC_q#UVkzBSN96CT)!bc#Dwt31d(7JS3{RPQi+>uf*C&Dv<89?3gY!^ z3u>`a&t7~38z77}URNUjc7FwnTf`Pzt48l6~Sf2#c#z*a4je?Sf$Q?GUNa$_|DP77xL7ID8i4{ zXeDLXz1`j_^5?OW4&BjPd^eg#-Yul6Fk!%PIcG*g`dloRQ+K&n1J;wfie_)!FUKl&uSP>^Fgsyga%TB*Z#3tSe|~N2La`pMXSF5K7k-1ani-4y zi`RC5rdWHatoEy1?QeBz6>EQ6()72v%DD7RycfmVnr;tM)8qN3UR^pPwFt{0jNKD`y)sW|1zK&amQaccz>p zYuSJ-ep?)mAF&5gcZVf^M1}{O=-*#lp1F-DS?g@6rX-bVh6`!9ivjB7MnD+xk;sCyaUGGIZop5tA1UkJ6Z4IT6M$!9$Z1v0FC4R5X%b=n9lv*rRVa7J_h&ZnGc$hkwWL|g5$GX-LKgPLqJP{ zyj3N;Jy#L}Ns-3vy=tj=lIm|w=ZG62lH#Z+%&o4e-@jAQ! zdhKuu;DE%v!F`>h(;LA;VMjnOyExlu*I*051&jZ?G%v;fFFk*-Gm2<%PUM?-E_LPr zPJrm=Fm?(uycH~zr2mQ@a+A0|`|9PwGvTwVKFCV|I_{8+v)aO( za$auwr#F=X0O}s$e*hCJ_xov)6I`2_QnvL{r3q%zmxl?bT|YVU^0_!C@$uxS#6#<3 z3@7@@krRMgI;Y*9998yqKROcY(6RuiwA(|Teg;{K<;#CK2qtrUGBk}HB^GKRyVSt~ z*!Qcv|2+2M7Ei5hY3y?0wFRn3MKr0gXUf-BNB=0n&NTX6O9>4!cC)R?xyDAIdao9B zrP4;+FlBLd!v*k0^O7F9E>8j|(&M#w`S{bDw90!J9erU2Ez&f^#8=Mzkw+(gtbJCQ z%-Jl01wdz67R$_!bo$}nFDAAj^zua%)CYq4Pz7oUpgstw4^^O8L_z5SqKgoW1`Q4P zF~tQ+HqE6#b)WM=C?%fYo0ZJ6&c!uneqgX-k1HzVY=)ba(N+crN&9oN=e@+aMsCC% z{9jLQ_JlTCd;ro1tzUbO#%}g}|5;*u_g%o2?6Wx9mUP^e%h+cT&bjZUINt<>BSU8| zy}>$YgXYImqr?#EQQZX>#-Ug7INbF<)vRfGjF1i^^O2`=rYAV8|G9ot`#j%5a159^ z{eq`2c>3~pt7r#pBSkAWOgb(|AmIBzOYL*&EwL}7xQRxpLCAT$zXS-pwO$u>Dn{+k zU*oKP`^(pDMDS=`?8IvUu6!i5#pT|H>Iw|E=x? zVyLh^@KY+6IXNtt$MYjKS9x2LV)85-xQM>Yz2`EYe+L|Cw|#t_2g2niSN{!8D@oxu z`NCdWC`!-^Gga|iiV`kbE}+!<>`=h97tnX3E;h&>Y-7%DmvKvbO zApPa|^9#Y+WRU2ITqK}GE%F*BmPG>gI*Ezn!9=xWOe($zX+#Ol?3c2yUWgK-ig`QzMa3#u z?Cxdp%!KWKx&BmdFGjFN?cL_aJ5^c)!F_0deep$KbcaLiRccNkpdeo)pr(eWmDc&W z)KzD+b4;jL)f(16s?fz%YVGTR?1MTDvKl#D?(rbx1U=zrs9U<3D~zZ3G56)x+WNtL5nS2bUSs_! zna@=#5V0J=&WU8(wp**Gni;Mo%XT;M7`u9-Fx0tA67FN}-kXL}85as|ZSJhS- z*~$}Q%bR-cJL1^}rzLB)b{1V>QWcnu-A37A4n#S6MXzeVYZoxpc5-|M#`deP2rPJ+WO24{ZLo0zf*MV<>Ag+ zvvKI}Be;6KXd6NHouA{c+1k6`CyG>5q`ver)57f5=Ej`=9`%0QRVa-gzkMsp?A7a6 zS)@q$k*;9ssHJl3`IEY${KiLGzY;zFLjMwVZ-a2n%*;V z^_su4unu>>=jdtSdvtyl4Sa)`IwN%bn^(wXFZ}8l1PvIn+Kzc7@FJee^xvm-nwAvV1u zF$y}Nbnx`B2IcimU7a|tM}{0L8wN4Xd>x^o+XgQR^LgfpHc$s~o@~@-45$iewo2wv zk!2itlx2zh$QMTZsA|aZVbqv!+|)NVOIYltp64xw2MKfCB4*co~KVG8@W`@R44 zl3*dVWe?He63g8YD8$94ZjWu^zm59FvdGn$r*ltdafvY!9)@>^hx$fGUmQlfK{}Cs zEdQl96Cdf>fJDf>n%TfENC!tsAwi@Lg@zJEU+V9YihsbiRf3#@E!^bPx#@}zW0)jb zYp1FLZbm=t#1#&;LT{_8Mz24($h9HZ_A1*LqI?FaS!<(dZHNwhu%zA`9NekZ)cb_d zjppQS|59gG_BGb5jE?YO*Ru+4m*FH;t$bx_Xaj)iF%Ck?0xN=WRkVkS5uEBN# zI)z38Iea>VC@KJ>Rr)$c;(mKph25zOG~K$ulK21s6aFaKN$D1;)KtH*7PF21sS|CR>5}_qG!%dv%md zRJSOA4*sgWy7833HCEPs?oQKBH+O(jnJaWR?!#^~(}$?M_e}2AXHY8YXee}ueGnB7 zyB>Rmn8Cj3urKpFHDh#O83uf!(%vuBtk1BF!zL7Q-UjR^N&lN3p*&3IEujrM~Bv1xSLmFT5P@F z3e?Ef2vcYk2%(%~j!dyYkkx=u3^ZDyiozhUylg2@l2GCVck7G0?sWc0XhV7PV`fNlR(-$jMf}@{Y z(4J;&5$S0#rY=nD=XXeB*1vYM25Nq-mj5K$0Xnp&U6V3h5yx;1CTnRPY}&b3T5A zN%Ii{3xqfR2c!~;N}DVteNkxCHc*qB*fnIvN4UtW7(W|g)8LX&g&GL;@hu*7v;aXEmYt(a| zo6}2<2n}M<=Y{g6x`-yB62hN`dsZW7oYe2t>+{YWuP9{`6D3C${3xSpg{#rZ39NF| zK9X@B4YkefsZc{&t!kd-=$OotO-yfF(yI{u0@KRNe2{( z!m6nTSd*XzV&qXnHOd+nN_T}s8zIGpWvF2GjoN>*5BHvwvPEM{s9OGU^MelZGy}ag zYM{3!8ECRrexOTn-h zYuqhg=kp9=)LdQcDrqAOV{#0kA2q5k`S`{^j%=s(+1h!d?X*6Jb|hAsKC;j2`45H_VBY#)+$3-b7KS_V&ab!E}k7QDk{;+&#r_divT^nWTrOfBz zKp*t`>#YIq@jKuy9iwG|_9{FvZoEYio4M;>C#^vTPT@Yh2+9ec<&*h7tX7T}2uNQ^ zzm#A6OP|PJAIkJbau^hkn*ngmwpsM4qp7)F`7q;7_n$#OganB1Oqz}{r@;aUIsybVVD zO1aG@5$jgWp$0{w8Q<|ncQl*a_WG?)EGxLExioeTwzY6pj7qE75aD!ejBpeQ9Hvhu z3^=^b#Q?hxBGN(taCjX>|3=fCyKOT6&R-z>Uxr!^cHF#EzQPwYU45YwK}D(rs(dgWW5>DUo+z`=aDl8ot9( zz_{N?oY}fdhuD>Eqnl!A=k`Ou-?xyl$S*7NP}y5J+ZK3agJ7OnNw=vv9hB5j7~o!O zfG~3=@r&AaD+YlhrCZw8QQ^;H?Aqb41ykUc+ffs)?^ zI74Qlh#_HHz`@~9K?g~9jfMJ+0tBYfHF3}G@HCGDAioKLKMVwawvc+hny6tPr)9CtAB!&Po`1<%gx;6^Lb=g~AnGd6D zvqZ#S#ui42z$5}OJbIX}y!{ob%yUmWly%72n?RlT#l!$Jv~rCYxx(Yb;3DE#ZBM`G zd17#$nqUR1@9T~hiB?*@sv;-q@~`(9QBXL^p~3-e*woc$X`o5rvQ!k|{Kz8{B1zqp zH_0ds%{_@)eP)1tEa@}cgy#m5-*gm3MornQ)EwQg6sc^QljA-Oqu%IdQJ}I>;fnjT z%8H|#Wrj_zt7|ZLJgDS{>9}v7Vvg0+3A1za^4vxuNK%@QNy2P zE3S42S9i(veq4S@+t|Z z%5+c=#-O{SI7irNV`v#oLBCo&tP{bLirHZ$N`AeuX$dVlzg6>e$Pb?mN zc}Tr`JZxRrf$b+=PT>&_*Hs7{3oZx3Z7g5op>mO<4o*K2meJ&>&s{=v0cps68zJ#H zfrcikMbuW~02bO><9o#*%Q)lY#`@yRMvna^sPeYZSx9fBdpS*-w;k~F5+rq+Y)}fX z?ta)wUMEZ6E+pS?Hky$b2BOuSYP?-**lyNz9g^c9qNGF|3GCuUWd0kjB)B`{syqDD zZ6~|y>$PNytM*WS>l!m%yN^i7=6UOC_H3?TVI9q{n;-~jI)QLSn%*ws zW`~`t>&paO&NJ0A+T0bV;N{3qC~ej85=mR}VW>X?nX8=S<=>+8b)u(W_d;+Xp}M#q z^k?LSGlv?SbZkmm@ZwYa0&!5`HImFTw~1@XA5g>^H^hm9PI}a8%MUZfQFf!>Z@+D| zoBSND3(gKd>*8Yrem#F*lzG^41jeuD?~8IdU{zRh z3m!)|abs6YEJ`RVP9|B<^HMC=jGJVxB}S&o0t(5Mf=2qrm>#`r>|2zN$Yp}SgjJ3;yFag@HH`hWR|unj+J5>pgzu|cd~}ax z`pl)ut|O^pbDqdKKblxnGP?xV65&$fmjjK(H3tgo&q?HpW6J481==bVM$tiOFHQO_ zxkCbu!{>@xBuH9z=Lg}bScsgdP_0U&RFESSk_FiVNN?&9*ys>-s5wpo@eE}7d@h0q z5vP?yQ-F7|t8Vv7x%kih>%kwfEz?3~1zCP^$-*tByqg~dEFBmZ0_(vLO23zgVmS)H zx(~9=RP_j);QPq>KA?B*N}$F zWmN`(&Deh@!DyyWl}MbV*WgSq7bm38eMF>KQU&EpxEypB^~*g>A}FP7nZR=i)VBAU zkEno2bMp?tx##XzBsjwyD3yW_)WCG+MY^x&(g$i_;{e}Z15=q=jq?3funNf4I+zUC zudjefGp2os!yqVNIgbHZ-jCqqy3_BafHlQi;@lur(#E_bm7Vrt#+1@hn%qaWIDbk( zynxK&JmLCczb6qctV91ynS6=w6ri;i-A5+U6R+YT&XT*oV&}9rhZI#e=@fN0{)l7{ zmqPZnRM^O3nKK=rwou<-42_R3aUQ9!%oiKrxlTkDKsmgjq*OS+D+lbHydH2~#*D$# zNBdM_ninl208WN#wPZ#)}OuOJYbgc4kBx+Wj zZ`8U~C2XoY?P!x1H)UiglQz!NQ)1>-MDUQkCUNCowT_UZuG<@S5Ud(=hK8S{e-$^V z_lQYo1+H)@cWA8^B8hCjVm%6J%1uwJL;oy-2o(c3;=no)HcZQi#VLAYX8G{=tkcHL z<%3N zlpb)3Fy;k59e8K3SCWf55~wPS+o8zy;{%`mHK<$}B9;!aQ5qn!QJ>a)8Ddv>Mt3g+NOLJ%%C7B;e!KHI?sLC~J)1$>7XU+? zFwQJ4quVFGh(&+O@egVWh#!z}P`=0);uMsw)LQ4=)-eAW>FV{XfyiEz;Y(wAY+Ye2 zr8Rr-ACYnxiNTB-&CW6Chih(EFlRo^`V?c0qoJe^L+(|?IYjj6wt2s}>T%F`R#%$a znwu1%y&c$Hj&S{<6x3`FnAd#^<;#6X_&Lj2HnV7umA|cYj>B-#<0Tv=oWa^dJP*&X zhp6g_Xk&*>L{TDl>SdE3BAJ^SOqCQFp(4N(2VutH&k;f#nO{O>X#`&K4OoA%13#2d zAygI>i$D%p<8oIRE9<9jT8d??@4`8^3UEm0kqESP#0A4Ydi=@Vi(CLoA%gLM=!=L_ zW3rL#7W8fZq^(jgtISX+sCW)J75NU?0+jrMBL6A4X5hV@XVJ`}!xuUh*>+60r~x6#mBo90BSPOzulAc3RG z=kUfk@3u~xRqT(h^e^vX`dbMZ^QiI|k9yD?M|GDTO7H}QKGr>JiJHK1tA`NsEe-5~AUlPoNeD7r)2mCeC}Vt>$KV zMgALIq=$|3?hV8ShS1$I!xIwdmBa+ohRcDIRRpI%vs|uk?Y@kN@RitZ?ra`x;-Hnz z?k;xz;#jfR0E}*Z)N$Y0_SZ$fsJ)s~c7hDu*8sC6izd@;P5BqfnDr`NUfgzLrm=V0 z`-A?h;e_OhPReJ7whh((mb_B)2sFgr~RH=h#lXizO(QM_!jQ zea8u%hIvevdsCqtSkS;^!sV3g6WG0mwgu*4ZVkA1g-hnKLqF)u<90SA`k~;l(*w}J ztx9fh3ir;t0R$UNG5=u2MBC%9Ae2C11+Z3aAgt~0@9r-rdm_~3Zg|m-p@rPwkO^H@ z5Nkz_;3}bFKP$Zps`NzMvb%+I3K71OMe)kA?KZ4GtZgxGbK~tUb{m}!rfjgdNaCo) zkm8SW@#y+Wj@Eef_3pNHhtP^Hjvoz&C@H5A5Vq4g|HQ$>F&z3y?maLssz~Y+Vf`8u zjX7Y(#$1Ocv5w=AQ7=LAEujrmyKBVeA+`kW20L^_qv@=5dD-dJz%Ct3Rxmb?hIw*4 zeAp_OtvavQzdpZ#Uy@s%-{SEe9_2d`miyh=Et|dU5qYjuURZZ!*+yIA3~-rUkH=19 zXDbq1wmYy!F+gwxUqR75p}Z#6P%Xa7@;gMgPN5l*x42O{*83_oh@kI~%d-Uc+%VoZ zA*4Cnhsui7ah>+sd52ej#yTbEgbAoaagf8<z7^@xn2FYBs zhIJu{T8JG@q|Mxu?X_mJv9Y#{=%f7cYpw0V#j;*1shqw_XK*U*{l?7P z@5E(b*c{&=4&h`xM23@+8%TP)|xH?#&&qPV{P)?vh zo7l$C72SkFcX?Zf=pnPm{r&(KIqEf`JjUr3C0ukAqPW`Xrjo*P)>CE(-^%LU zt}ne>c;df$XNNQa=Ls$7NZ)LG-O|)TUS%0{mo_hH@xXF>8S?(h9|6S$#>zEO1p^9Q zGgtT+%fUcTKH~TuLJ`}q>ud~Rgr)%=Uq*1kHQQ}y?beQr9p%FD9CMg89JHE@whp2yUY2_N=mIVptO=AQVUb^nW2oivXZ70E>^L7*YoqK|su&329&UH~{;Zu}={*o23D z^;|Wq|8;(8A;tgZVa(%y^Dh@t{BIsq#QzXJo#KDFA^u5%p&|Be^XKRfh~_W3ZyP0SrH~o|1Me}SJZ0_0Rn$gJRfytt1NUb z2iM>;5>>Q$s)J>Vrh(n>46iRb`~7PyWvBhCzRh*#FoL2JzRPSg4>)B#@`7Do&Z>pDxZ!F5$bf_4kmX@Y(Yc>sf&@XOcPo5N`TBmNFSQ zc@ld3w%>t}#a0#~V< zi%NN0K;u1BRM&5#0FdAEjml8wD`gv%u`h443}sIbF1u(^sRY0BHPQ-3+dUDdTA72^ z@KeOs*t|oXWxid%?un{CJ5x3RG@Nk`>AhLLi3?#+#SwD$_VVXH++O~Y=AZ|iI8vH0 z-7_Rs>Qt&qI#Ciijry*0ewlpOt0heh5#qLd=B!kF#d2?d#XOtu)bvNCu*xZmylOE7j00|Tao5H`& z!e0}8V|KpW*>+e(8YJGkilY=49LvWQ%Zg~(mCHCvbZZ9aX0@BsRvTKDE$Dfts_9a{ z<_2&wD+>0ftKnQNF;d8iIzRu03u`jFN-SjAolBMTbq$vOr^%{Y|4dO&6Ye$DpK_z} zcqVSpEt7tfvtM^`H<9hB zm^rn2Rtt%Sg6zt%CYwb|-GrsO+8C9|t!xf`xUPyT)J)oSKAbS4V%$uJrG7G=yJ3nB z`sb?E_#Dq1+Yfu`PFSfq_zo+KgJpo;u~o$Irv5)pUv@y?oN$-QV-k^o9Q9b%N%N$^;pe$p?ir9A?AYvwl|VQWR?sxErLQEg6W?##h!al0T6pIc=_W0zGvT-F7tBl3| z2)QXb382-SFht6c=Qq|Y;&19#&xp4p2i{c3KJ{+t0MQ)4@OzTnV7sIs{C1IAu!jvR z>^gi5CAScIq(xWyQh>2YPY9rPS6}$ACpRp>pN697gi=ZT5$(?Dto&#l_lM?r__1@m zipVj-NTvGRbJc0_VFxMjS!GeXFWD^xSY=~lU7Xj zbLdNU&$3^lqq6?i%Y=0XmcE6znH1DWMiCc>Fb0Qbt#h7n2MU>D>u=RQ!@}%qb7k9t3Hk~`@YuPTSL|dtWiq~PtbzzRH==o zy0$9PnbH}tXjQRuqhtW{W~H-sovl%u6>KkBr#Gzu`-fL9c@;|_0`D3A2GuI2stJSx za4%XzF@+pcux^wbH3_i9VArSj{?ejEm6q*;y=!>`&F<@O9Q&@?J7vfy+6wn>xf%J+ z%Di$ce-)$%O|{uM?exK=7G$^ESFe48Zlj@=;R2O!(hnPKFx7;UboacSnWHj^<4KAF z`8n!L)5)eO02F2<@GHDyIluC}J3edmPCFrqwn_pwFAW1sO{1u?x^UIOc=P*Aqt&qE z#zqi)a|d-r@|dYzs-J<3Lqqg!E;LGCICT7Uh#B0CDjg6glM~Lud@Fd5puN6VSXT2r zrKjRe-MDGGemU5gj*ULBLXtZad@sRu)^jqPeO6R#Q{_YsT7?}I>Dvz%D%OJLs>SMu z`j_0SziO>+)H6468=JHGIki$qo@Nu5v{(A+QX*`s@;ggb(p^isIh3DaGEWFEznN7# zbP9*F2gSpgBF3IESkPi*=ibfbG7Cr48D`46RILfoHo9ZSC_J2edD_M>#Ea z?rskk0gQ668?+zg+z|Q3XNBON6QWHX9TVzZ*c0Gl29?pbR9N3k zKsZ-%UF&(p`|B6AZ)7F6jTh%i@a2nJ9-w$6_#L}NCd%=GIrf1}hFcW#+slReabX|C zHJDSpJ$TqAXczqIDG_t1iB~vc0NF$}R$*V(%^)nxi=J3O&x(2t65p4`fDn1@VU#&$ z7wH&V5@e^jTlKSK)+TYgXADnFQ^B`}B7YYPZTn1gO z@$&hene5qp4%unV&GZV(UYc=yo3-C{(U{b5x3183FZ8N>e zH#Ne^eX{K=eW_hg1K&ea0a#qk_jM_I*FR_UB*Xo4VqCQhN362-d|lNXEa+}sv|PFg z1N4h)`TaPrbV)2I17h$g0dk@|TGrJUO452=OC_^uREECp4%$2J=maGxI5P-^{}m}4 z^N^Xc(A~0Y7gQ6k?wKOp3VVd;bl0Jf4J^kcf5f4;kE@E0EdNa1cY*Wn8?@pfFfQY4r+=Y*4m(R za^1nj#HV!`%`AR7VU`QxSy#4}W~r@GPr?btHn`ki;+(o~#CWVNSys9j#O)^>N}3o1Gk+d7 zHfgJkqnt|;Izc&0u^P}iKnP@u*}`?RbS#L*;T?7eR0$oSe(2HAP0jMb+PEs1ibn&E2nw}ysLx#u)EHn-#f?dl&REE z`7S9Cf_0+iuzfFEjSY0t6V!(^sGSH~HX~JJl!4#@gkb>hw&iB$YVyTO88EjYcbM{H zaBN7?;LzyR5zsG}49)UO&CcT=0*dRIbV@$*m$iZ{zetfPZ{;V39fv(k;z?imPvOY& zUwZR;7!FBmk(^J?{wIGxOl6WNL`GM#nY0%oD&-55U@R4Z?&{_hEb22W=k(XlKwc=6 z`i$~{-ZkquNm$6p^D`Uy#ctUZ{wUdkbrp4ZsyXnWkCRAPZ2$YZ(>uO{xhF0h|JH8^ zkkFqHs-xSf1&1bP8{;nNNeN?XQNN^g7{n|f9e$1}C?riEtIQ5QnYs=YJFS8uhoKB4 zVso_z&lzZe|Fiic9q0V0oFikh@49qH#yc((Z5U7_J80D=KAi2qF%H4Os>7*oa$I?2 z5|D8gFc0)qfXO%qT^lBc8M3l^i13#On0nudah5CMvy@hlcynm0yj%2TvbqTh73+9_ zlU1T0a3qC;aNJ@C!wqf(S7eG*st{rM&oNR~^DtZv(87PNTE%&J8%*^!Q(d+fL5JkT z`Bh1J(AaN$khJsh{I4Fi#`fC&=E@Wm{?AHde`~kN)qS!SaGT0o9Ei0`b#g2i?yq+Y z?X=jyf~7=MZ@p@2K^(fE-W)KKh)%DI=YR^q%)ps^D>R$87GeDi-(6p{dhapQGV@S{ z1Sgkx}JdTp7HZi4FMj8-*%D8Z|ep(+tmy~argf{nBWqQdwe+xL)0{W%gT z8#+FOR!tV?<;-fIvnB~ntVq~`q$i=GWJrxccDOq_y++R*w;HB%B3D6&)p>%DwsDLR z!o+PhUvT&?E^(!ll;YzWAM<4}h@nIY3}`;5eFQxWhbTGEzid!`2>8iIpLG+#+lD?( z6hZ$;T>OJJY>x%jaEPS*AzeRWz_CnTv~e>_f$?ibXvmVH-&~R?mia|G$;H!HvpS<77*jD} zzg3Yo8&G?}K2}OWehjOL6b(X9(P{&1UaA*K9Li?87hd0^L+?3=B5`+ws3y1-X23co zVIKMVp_UE`x0noKLg^QI4x+4H_YG=CWVlBJA|OweA6;5=q#^%uWf4;u>B=NZ6E~|l zd(&DLRtax?!;!R%a5NsQu~rE&{p}*9qH9l3Awk_@99mPsu)Y+N8k5Es_#Z|WD>00t z(o45CdgjZI6=$7NS+Zk!Rq+~i1$zaBtZzavRd zy%Y$s4p4uOR7=b2h>@DL!XftfuMPwID=V9bgLuw|=8#JhU9-Z{LE<0r6LFe`a{$Wu z1#of%r?|B(O{wWQ#t#P4=w-Qx?L`bjY$D7(qhv)%HM3wqSNh1+!?+f#V8zcm8{L1yde+5Oanpf48p@KV-rA4qB&2d2lG7 zB7eU;yE1$@YGd6hT?C*$9u7Jeh(nb(vdx|zUJY8kp-nEXg?1z&j89mpDF95;0diM7%hcuFSN`Hyt)KbsVH+)6x4XnM zQ&PIAiC6P{S3CFtT!nsud7O`JMBM_vpuTZ~gF%avrm`C-ANwb7b%YKWur1@0M->N= zI)fYBuO>4i|Fxnc3QibZx-)G@GWD%H!ja}K$`Hh17^U3@n!aSaWw@;x}dO7 zdtO@2v62H`355`dm0`wqXqgEttrQZtNV$>hjG@l-Wb-^_-Im|7uSg>{jT-WDYA zKE?MzG!YewTz;JrKb|^m-KCDE>G3)CgwxCJdH;$XF|7-l=3k}xx^|SG>e5lds0ToL zVYdfY&%&(>bk)ZcoNxoiMgRuZh`L0+!3YTBHusxK7o8z7??{e;sGm9_itdxRpp1{; zo_#F>sw5EO8P6g>bkY1rKwjL0>zwap?z59?CVMh<{oqs6$b9h>6%Cby5k}&i5YB@~ z&$i-b<)dV&W&lTLpg{~;YDe~gmV^PKL9a!{ZR{6nFX92WSySB>PAYVe7;^H~T!rfP zbUfLe&cJS#75*s7hBz#%HPYG-ATqNtf@{x3Mgrx{&bih!N zYOO+m>E@VVYzhXWc#&Ch7NeV1>03zr51(Mle`?wZQFs2HLZMpXKTO|gD+LBWOOVVW zQ$`dM(G`TTuVuu6;xQ$001U@z$~B$+AE?MkA0I{^*Pj{J^CF!`N(zUM&amlsabFEZh_-YpVjz0Ki|?_JY%;D`o({2*GxX05Vl zYTm-p#RBCe>F7djQT}F7(h877?dyTa5u}x~NQpGCR;r_y+9;(m()gFX)_YHNR+$(y zWxAkV;*>j(kx~DVGk#Y7O*cdgr7J3Y%)Ab~L0wHc9$wiTb_|mLD(KY(I2EyNG+L9V zVQss6Jm?SmCs(!A)=k&WDMf&_qjg0Im1l4e&qD zv44e3MdInhDJEpwG|{1kRM1v<`yBr-eG+7fl?gA>Cx6HPvMrm8yRwGequnl%X`3wS zY4*g4GcnyLfZYpn>Z|mLqopTmiiZ1_YI_+bMi%sTMd^Ul3t4)Q^@McAG7eRUMEVPa zd!`y8bH?js8-woYX=hMFxCc}{HC8j?e@+~iI%qeY;}<$tBY*y$on*z?`xkJNBUo=V zKu<|RGZ7yRmADG+wI6jq${1}Qgh5{j(qj8C4B=6I871`87u<9#?&tBX?%{0?H+yn4 zM1b&A$I41Wglwo!8XonG#bo_gSccv=*Y=yc$U852i(IQbD6D}8(clWRmUqta<^*$zOtky}HZQTl ztJF_jyqlFvFnEw;S1+_vXk1sS-p<5v*vRBfPRXbkIxq1WQV>8R!Qov65Uicg+4Y>o zb?u5iM!=nuEGpw(p%vhf*Fhu>zJ^(kgL-BbmGPZ@+u76*;4?Ybqydxw4f7AEew*cP@kzWEX(zQPc4!Opg0aQP+sp{5P$8t)Z8`vGt@$WzG#2~C65f@$q38u5JZ$z-lZy1=MMv^ZS&X&KK!Sea!#;rH4C7a4S(o^}qcMpQ+iy$&O*G zDu$s&vCaTj{It8Uid%!~vTyfSQ24Fh0;Fk;stZ4zoqgJRHurQ6Rkr&B+#K|DvAXm> z|3T3I{715anTY%Vqe`M6QktIP{OE{c$@<%b1rbvOU-glug-WWy;^uIS>N)Q>V8 zRTx=gSy(c`R>M4KAkZA;4sckt)vGSIxw6$nvCY~_ZL8LVB~Ue3)J8FPQD%2Usc!Gx zDT;M_*p-lTTicxha(&!Y!~J1>4HbTK&5fBRPiru1 z*>)RFpD#EzR}I&dyGCr#n4-yoFdyNKU?V~8zgWPzR50<&CC3N_>!LFpwoW_d1W5gQ z?F?q@@yZn(+}1Q#3)~c{t%TABMO_m?Nff7-eT#p4O~SQOcRwh2#jrTAYPM!kOv#>v zZ6^vehb*L!uEVh~3vzcRHCSeh`)cloNP96 z*tp;ccfC}|^{O7<=H$*YI@{MYRj&B@Gc5T2Ia+@GJGX(PI)F6Ff1U{q4s8cojH>u9 zD6Q@}Em*WU{eYBS#-TGhRMVMW=M?Vjn~o_Qs`t3nwbY1Giicc(wVkz%#=+)~Yi411 zBY*D6)J77!iOeq~v+r>&Qbv4R-gRH50cCsGg5of^ctHOe>+75Q+rL>CqvodQ<)9D0 z*8@7azq_%v|67cpG*svqN83&H`|zM_G~b)yXmyBXVoiJZY|QIoa0FMaiQC}oQ$r!M zsTn?6Y2B14LY4Pc#&%YUh4h1!avLkfV)lh=fXXqks40`MUWmEuckI~joOCcL;)|#Z z@~%tmWM=Q#e~W$Af>x$jM2mM@d5Q?b#Pi1KPk1@)mS;?Acubn30`w(wCO7qYulz$&(gVgC6y29r7Mj3#^ z|4OmZ9_KbeSz4J(S0?aXP=63t&cpk<))w+yMW~Q}RnelWZAEHDi=e>?YadyG{}p_a zL1j?y@~mk-V{`=P##2H=<31NYS%wSzR(Odfv+gfpjQOCOe+Cx%lLRAqjl_4&DHNVY zGVB5}vwK3%8Ovps!lTZev$gHh;XyPETb@dbu;8>(FmKIj#&^J1hz*GN36( zAD4VG97>zW6c5q|qmgY36Ibmo8>grpe43^m6wkDy*om>HKXK7#gBOj@T5tvF!*^Y% z{n8)jW)*U0h6zD!r7|;ODsQgwM-+~3Ktt98bX1GXeD|Gp-Pz!fQrZl-{xG#{y1D&N z&D0g`f9eaLdl$wg>N>m2@10tLFMDsVZp&kw|ahQsCsk)UMe&K-*b6ht{E2iW{U$F~Ad}SFAD49V2ACqb?5bc7WXbHQ$+09mbYAT4gx z(2PGJ*(I*u$Zihp&`IrcUduATp8c#H|K;Z$cVop!dz$sk@Y0I+Fk^(Xp0QK0^(d$aNA0Cy#Ak)5;h+41zR!M`|4 z+8XBAH)@?cOsR`0cIl3Z=z`!-m*_j2i(1gWIH%r((@JaMsI6q``idv{lrR3(CJ#vA z&VtCohV}l!zK>*QJe_#Ru1-ON|hjW1bL2&>iT_KvcbVJKQ~3)kJimi;P~bZ*x&YlVvh-4ZZ7hPtUGq zHLFPk1;@aU^(9V+BJEPoTt_+(#C^JCGnx+t8|d0k(N}V!-iDAa?Ud>C=`;kiAA%H+ zj0VAp0w={zy6Z?+`Hd`FX{APsvz^u;U2E_0&koKCFJE6`qYjQ@5(SnLRAaZ_Ahu>{ zVe3qBib;KIQNCh1ktsnGr>G@xyg00x)E0!CV}gIx5EjB_*R>)ZIz+B=RB@6{6w zb7y0FcU$Ah9n>jN^4SfHaprvg9){lf?!JfQP>Udrqm)O>iG#<&tFg1Ow)Hp^)0y>H zh&2yzJ>5e{w%+`C@fQyuqV+wPYj1aR=fK0d$M^x6rH3ICPhgW74iEQN`&e#7GDa9F zG9kb!7Q!<5OR>wKVy$Q*b&Svq<=N1`sszkpXfru9Y$e=iG#dw+LOjESglei^`2v(5l2x9%zcMM)mIoeWy7%ZP2;4u= zId8*WfbfZd{{L{0RuLSspH*6>8KiyVbjd}8OTdsGWaQwiFmMu9nV%sDIZRrE4x(g{ z%&60@C99p|PrRxJ3802}=9v(Jy#LTdELB@+VAIQcLdy&%y23|G?84l!;w2I_Q`9Tx z(xEBZ5P=r=Vt;LA7o+y)-Mwb>V0UkCZPib*g&J<25?W2G6o|N7U0eBn1!;sMh!hB; zRK^G~#|(Qd9H?o(?_G7z*RKcg*9~_12!X_Qhpo>e*d%*ZG~~Do%VWv;3kW$ZQ}&zUH|%z5R($eit(hvk(Z^9 zEi1Ry8vARja7nmvMi9H@g)5VsrtB*3>vr1G-Cz9qj&fAh zKRuOD;9T?L>cqel!j2Qj;}GT%arj-t+)l|=(K^0mG6^9Byx1u^YVdU7h2C<@JEk8; z=IpF}SbzI_$(WquncbU7(Klh-2QR%F`)g}iAKQfMuI)=4M0ZbDkCL6Iriz8@ub4hv z(>?Jh zod>hYYu(nbgKLFbTd*;mI4PTXrZRCbO?kc^3=p$-eg`WRR&c^A2@_m-IYh3GWN`r} zQ{+BDRTThV){_&})v&Jz+FZ*i{b7wKGqBg!Gc5crpP<2i3RegnK;P?-y>PVMZeLDX zUglu~c?sNSp+{*f7T{{T|AM9U!Q97x}xat z{sxQ6Skt3{yy!P@(Wu9sWJ-?)$7$;v2j;Kaos-l1GAS##x96o=#hnUraRCk+t%f-xD` z{#ZJxCHtMr{(#wDJIMua_;nOc6-uK5Y4OI8FtWa_Vd z+*?~Yh`thi6!t55bt)kRvYlYXLjua zRVH>-DhocSH;+9yE4aXKYjf>jb9-&VLHg}ILk}6c#umGFfCGPA6m?;A+&pgFAU1;& z>|X1hY}rjnSGM*rH`@N%-tPWEb8T%q#BhL1NBWx^?e_ZO0s|Frc6S)x!6GwmJ$_?; zSO!8xw~g5TE><5slFWFjhF$&^ZZM(MBTr%^>2WA-J*oPqkR^MNUd2OoQL_uVHS82w z=A-~WZLY%k4T$nWAmX!vq8;S2eIthAy47xX+CSs-FB0i0(kc61ZqRT}=0U%IwZ~K> zU=_AtyPG(7-Ma>%_VL-b<6#Yr+8T_^3iF@t#G5rRUM9g&b1)c|tU15URkGzGmB@vFRuvjXgRE1gg;hvBJir zXPxs)8OKTc_6`+hkQU#@U)alc?*d_v%q@p&op89+wZXSjwE>m;Q^IU@2&O-D7-m}< zUi3e8qNYmFH*SH`J8;NMll2l{15K#s+i(~v2OY9~s$PPEyi5HI)wq16P?v+jtJqpG+$Yr_OMREb2;l0a~&54vyqPEs({(0`nq|NWNcoj+GrrSbP z_#?Eg_Z&~&J~n+f-tQzEFfOkVp!^6J@K)r>+kc0s{b91(d%k-DJFJZOQewAXX6 z-pKYH#dK6})##qL3vkNFMZw zmn#&zMc{}kP}!DFNeZUU@ZPO$A($V^##ZY@sQ) zKx19tAqgs<8BNIEPc>p#vV#Wm*Xk^&A8eSp^dA(#QDN~f0hB|4o2Eay0P0j|` z&CwKWP#z)#j0Fs5zX8COa>Pm(af+w}-BPV>@zsM{gE2d4YfBIA+tL($TY7NcmZs?2 ziwF1Zg+Kk}Mjku4xY?nEx|OU)ujIWs?-f$o^-$aRPFgSLJ2q2byXlDakfunf5~`GP z_{)d&>g5!@`exxl1pH=-UVZnlroMXwR&zE7bq=IWyr;N(_G00m|Cos9pnF0Z=eceg zR(Q7f>OpNSPQeQg>(vyTF#o_#q5kBPY5swuh5C~>_I_SsSYoq;>owQ-a;fy5<#P-& zd2_Pqop2i^+01ftwlyi|7T{cKDYiT(6-;q^_iqs4!S`zL{w=Q^{uUq>n+&sN&Y78Jkv;1q&@sO3@E`rL%4MP1K%Mxp$MIUt-4+ zS8i0ahZ0RKaPLN(XqTIpE1EGfN|9Vch$~eu82q%8k`l989EbOA(f~}+V|oPEFSiH6k42q)O zo#woy0dWo9anI1LDbdhAAuS3(w)@!CAV!_fxW)b<2mlF(uG=amAHfy!+)vZka_PzTpwu!&I@y+{n55BbdG?wPi#ve?x^+5OC~zW&A~D7*TuezhRkryZq9 z`tW^a!39!5t-^*m6LaE>((IMD=vKa~DDj=e0bqVRU5l*HznxuKNLRkoK&2wlHD)w^ zuVWv-(IHncKv6JyCI($EOhmWKE>b0pX2pGW_oM3ZEDCc=fY$CY2WKSrbE?N1!9$Ln zz*n+^)w*rnfv>=#E(TyqHpQg|7U@j@1J#{>b7mFhc1*&vpon%589#1tT^*gTVR+NN zSnvNPqnwkpHs(xG$@J}JBW^z5+}w1UoaD{J9wJwC9J+X_63lg4$QiQW%K8#z($&*f zk(i06mQu2=pr#uHN{(m0FCYn)cJLvj7Mj%s&~(I>1t$>uUzyDDWEQmRZfS5Uyu7uT zo-$3(8-i)R(3zL-nHN_+NE3XNMqI$mE^R zo#SgngnyOrT+vwz0{;j*v-o;}Bp^7gBv;iU+y$mQjQM+LkwPt)`h>SGXP=_QK=kII zsnpuKZ#uN=SO&1sV47;+o4ccBpjwV_nt!(}>-I=Ux~MBUo3O=ncr(b1V+!$Sn7iWN zl_SicsC~ZrkvD+vB1MVC*(qtPdzqY)s;5TOwJgUJ`fyJnd-69ead^4W9U%`+(v?8#Xd2Se`OqsK){n0|S) zhcH|-xTj$`LcoE-qy7NLGb{1EH-gbzr0?c`;)#cIG)UB70ooJd%v9Ur`bwngQ#h%h z&_OWtJr@~Q!5J1cZf)JKAEpK1st<4}4*!PraviU*@WRxO+G=%Wx$woqI&(>Yd`Mm^lvQaaYXIi(2oIM?`laC2iMM! zNq{TGDfFS%dB1mRH_#diRdRq~KvaTK_C)~+|jYgW$UPa&VzThiqxpF4fGiJ@Tzl}v`+Z-s`aS@^I4pL$nScAz9?Zi zuLHWOiEZIsG{bBbA2x#@zH>RtivA#ktu#$ESbMv>g_Az+o_Rfz{sMB9JJcsKtfT(x-)~& zA$<0~8Oud&m$O97aYNg>BZAz!R)*K*Wqj@bMf~dK=UDaG4mCjCcIzDfR<3n!>+^rz zQdcE4qLm~vf$|F>*AFq_p|v4>nk^e9d8o>fF_Z39Sl=SJTtn)0ce*5&^Ub}@ogJiI zP<^YcQHlg-V>#yMz3Y%yx`;-LOs%@1xBF|2@7Hz?_Wz7qY@SSn8?t2~b6{yq)?R(spyx`64n*|BNl=4oMcT&-0v58gnre-Z^%?unw zrlUmUe;BR$#m#2C4;h0!(@aD^s47%VGmZq7jfDQV4rhG`Oc64~a+L8Q99xJMZrY04 zW;^k-;i48hS&)OBiL4ljO7{wvr;q{|1QcyqYC9LL&&D3(iIbe3^@%KR7xh{+HZ_>p?5n@@4kX|m<+ifs%|BH5KIB}gxNs|r0kX0mGvxc-` z`v+LhMtWqU7?sdVBI$t99gDYcb**l@8pjs3v)t>f}@n=gBnF68al#oQ}v5+G1|vm{z4! zCcIirqcrdVBkJ0&jKi=hh@(P6B}`G-U-Q&wjE5hVuaJ1}7Kfa{xK5fmv#-^=L+D3> zxxJ|hXDboLYD+u_WqpjxRBruQK1DaT&~DQt)Abd4Q&cS!4~bWk?_$7`{XvcWUL zF>~XPAM^)ZAQ1AV=Q?6Fm2GM?5LKH}{YDUUH;3f_O7=CeW@OZr?m4mbmtM1T4ihnv zE?H7H#JHW27tbLQAV6oI4(Dor>CL1H-}E<<09mS7EPBIySDD8FlaGrfCHAuV(u0>K zqMli*^wnsonshEIA!9&TB5@9@JQksbw(J&o5>bsiOsz3OBEOQEXO(&%ysO2vLt7HO zirJ;)^;o?w?B9UR9Gi75Wd zkwB@j+M1iRT90DsTB4d~=fII9Z7qCpLDiYv7ER(56uE}xT#KsWzFj>8U%vKaTwCx_ zc`HJ0x5y?wn@KHaRIa}@Cv$`&Mos7gg{l+UW@t2#5l0gGtWY!4Sc#z@!fejIl}SAc z2g))maI&H>y8CJE#YwXL?n~0xh6WKMASJQa^4~gxii4FKlBm@gC@G%*28>=EXiaZpEv%G?+GYrJ&?{)CC@n5}E-jO?B9A%ML{> z={b6lCzt-`Zt-gAQOu;vs=r_w6tsnw)=wP*E^43_Szgj(4abQPa?HxxneFb~u)?B) z_AXqPUFvp}qFy4Se z^m7MER-hQ$2n{)Za77`I^UJ=F;XK3_Qh{aG4>BE=9LnrO z8{xQ6<%$K=lF*97pNjzGKne?HtON~%GxbB4D&roL5v&pUF2tdvv(*3zMe|}xYUPG@ zT{Zxn)1(-M2U7agr^Iy>yL)F+9CI#hIP@FWx(;XzLzG>#u4ZTc>^2XIEk7N?pzQgqftm9T`X|C+K%IZVbUwpVfkvd$}=lgs`Ul7!LKZHOb!F1OFgCtQVyHGjiD z;@Xg#36l$qRMeS>N2PQAwqTrHx{@^pD>QE_vu4u2YGv!NFJ5=8AS-e_+6rTcWWXMR zTXJSw>5*ThS~UmY^^yNcNOPGDAN5AYQB54jDi#V{?bgLgM$+Lt#g$H1=5!_%57R?l z00L>Q@e4+T00m!-9xQ0=N=f8D2vtk?N1Mtl&a4KNNWMh8kQWl#V1AxbqTce70w-l8 zL^qzI!q@aELQxZ;(ZB7X!P`lMhH(t~FVn#uh4#X)7RcMI(v!48U=350?n7J~7ljyx zX4F`l9imlY*^wWmOu=ZfD;Jx#m$(gpGg%5ka}*VF-5=L{Moef|Wz_16DHeva8jexwrb(na&Htf@0Anw6 z0Y8R`z5luUi<>x8;-qjmw}mUCwpA*q%r5m$u5brET}5gmzg~l+-EQOhR6P-?|UgaFCo`<9LvdjXjZ-f)TS2VDu9rydT$< z@Pb;b7GTREd}dsD0wb4_C17rcw1QVGo&f(Jd+)m3R&uNh{^wJ`a3~CRFQG|FYDwIU zj)6!}At%VCIb#L3V z9c~J%E?HGsS(#Z`Sy_&+hik{%8qzStYKwOi($sH!UBc{WxW>USbx!%v@g0WKCJpr# zcniDgE=n@)oaiH7-L%r8o2wW(#(jxN@4Vk|Ta6KZ8Q(g`M95i_N)&BdK?JcPg&J?< zA4(JXQ5TP@DLbIj!8hZ6e4a12GHQ%IU)Rg=ZmK9{zP24siVxF%iHM&X4(1o5gv;pw zWu4}jRua0eQp0^oI4h1;!;^o5#=0UQ!uOjiJT;53n;_6v-M#%zeX3|lJBBxu^#XqF zI*XNt>mT0!^wsqby`Mx;fQ@<{|M=m-=}+XUB>%tuwVGj{UhIhTr--K|*&)dEy9?`r zsb67YXAeU-{S80 zJ5LPEDtqh(20*YQtP!rT$eyEwqjp zPc>?Flh?pmHnxXo?JzE`0!K{+Ms+N1;xD;E{~d02FRZ5Y$MkwF0JR{nJ|%Te z)z+Hu4#cC!e8tQWio2$1Eke2c%2Dy`we{mDW)YR+ZiQMH8HGZVznQ|cq|^R2jE_^< z%4wkw18`?8ERhjxOZlqt2r>79WvFzq zSFCVDgg*wA01eB-Ha%!&|b2yceI z`^5-70#lpI&%tF*d)K{6WdHRj3vA|BU>|c?NC@BhU#;r|93%qF$u3#CoT*SwiAdL$ zOk0kDgt1YmrwS|iirvz5%Ei2yZ!Gv9PPY9XM`qb?!9#cJQJX<^5;xK}tRo(!#$MD< z;<}+3(1)+*(AHb$tz0Pa2OLYO+H|3W2@PUS zM4Vq?b7@eV+LZh_%h}H{W0&wgibN)x2@@8oV-bF@;6T}o-wn~apc7UU?h`7<1QX^Z zCAUy|PG}#oO1sBCq`QrVkDn&2O-qO(?k<2jy~mX|<8if6-VD|G@;uhU)D+8`r@bIs zSHqz+pN&Eh^AY#VMy1h-Q3X|F&Ra691>Y77Wb(;9AEmQx;S z2j|zU=E}nMiHm$%AG>|;lsV@dUr=nr;`?HJ7ojF_oB@+Nd)O&;j^N%Yv<-9RN$CBK z*Z@e?Q|rC0B&`JZr09BKbL$Y_qtB zJvjaR!RhLzi%*qR)K39L#Gi*45*;HMmbqL*vpsni4JsbBIV6PGQ#KdCh6rKZ5jfg# zD+7v=OG=3UBgo}f#k$VX6{DGy6B%aMA_jW1-y86&aj5$kB+q?DjEPP;Nc4+)YA>Bn>1jlPZ*ha z?kLZf7I!v+AaN^AmRaEIVX+xQ}Y_djz-&v)Cb%; z=h;=Q zq_25Ru91!8pP99kVPS8oxbF7ePD{e>S*~-l7G70tTeM;gE~P%#IF8^En|%1X z@kCLTTKb#}aZGLW4(Gv~Tks@yQ>Mtdh4Gqz{+1#+CeikUr9Og1XGCtPB7pTaofg#< zF>Na6dnxdw$#Wn1)VxZTmAY zL8zA3eT^TW8I9`R>9DW*zNS>kBH^Vxp5o;dnO=`F_5ah^HMoSw6&eW!#EO`}i-d=gZcE2I}eVsnESC}}{P{hx$&(OQlGDjc_MF>WY`JC7N zJW%rJv0q#jJmT)hAsdQ>If49&*8zNcvX{_5`mFgJWp4|c6>52I8EV6}zj2`x^;!6& zKjz`D>vi_H!qd%QTstr9)_{XVF)CLNyina4jCw=&Cc79H=FOkPs0$3`VJ?0yPZYsu z*jlVNWn?__z;tYHW$RrVXQ^W>!sE=B`SenX8{94Ky~60_zu*WFOfe>gbBG=%iLi%!rq^~Ahj#v8)8O)he$XyxY!M5B<^^383# zeMhbcvUv-;Ps)=;J3o7mA>Qdp=YGr0pU^J?{HWAfxSQ)XqzL(17q&4RxAWef-@P4& z>|>loPUF1Sr`hYNs?Dg)%|$J2n6>#AqwIy-nSe*$0MpkU+_CT1UVDZ5_4xF-nep#& z0?h4HY$Q^wxXF=Zr<*Gf)hwe;rX>3^+c_!x2bC3n$XR_eV;rFE;CL7y~f z-d$MYb)kfCcW!%~_8a$c>UU(vC=0+=)>p~ z0326(zW!?~9T8ob#&PZLv9jw0C54ovfia;U$-PwB5wpLqx3lZlAgQ^7W_9enB_jP_ z|LTQpR%z(Ln3J$VI!bu+;Nc~6g#n~j00FaioLwIWg^7CL;N*in-y&9XTYSUu&FM6I zKY%;=_{DM(XSj>m%i!KEi0VAit zAizR3(oR{@e4K%6oeZu9#4>0isy(J)J&mnD81UBZ%QjsqzgDt}8&G>kyRUD@g;TMEULIfd_fKn(r)%Q9kG)2NnZr5X()F5-PT z)^21eb0A?X>6LtRDA6iNM51Bk>bMtka+P*=q?TiU%WP<(9(&S7eNJ!r6q`G6(;g@P z7oTLrW0WK5OMPNp0@_C?$z^-Al%pnnPW)dSGgJ3^dLAb}V?O!&>dTS8LLChI(7NG# zm#%?iq1Jr|++DHYHaBCLlmY)O)=K*9`@bSV*oKqRR5_feO4t6udqI7)V3;$8zk13s zH8th8bt}3YdB{~K-)x|fPV?K=_EM~}48q}2w-O+b<#~o1-E?QOmFWd`Bx_TXn)%WtxUPY#dt^;fJQiM_pMdClO8v6^y4#oGR&@+@);2H zCaRElfMFFYweiih!~~ak&vW4f`CkNs$-{Bau6A0Dz!WYzgV@}|vnfJSXPg z)v`&ZGeuN^$2%@woDP!)|Kj;hu1a*uBL^kPU~E_gYfz^wgcFPl+XpRSrN}0Sf}T?xc`zi7W$j;8 zQZaIt3sn<`Iajr5Zx}4BZ0BM+sxks#PxWG&Hp!!RMz~`ziVig zpAR0$RC_v>Jly!|59dMsz4#+fi8{cMAc^v(hQJa}O`~pgpqx5-od*+69b#?Uhbx%j z<@5`2#q&>0MMH<1DQ}&wW+nGFKUe1^i3w>?KgX-+Jg~u)a?}hCE-6H|9~@jzu))%E zNL;T9(`VvDmWa2S#c*fxNyY=-Ywjx<>k_3*vM9Q`w!u(hZ@Nv9gm?t7UsJp5;X!w- zcHG*d`jb`k1jkyaM9+=hu(qMm(Bi)^0veMW-d>!Zm$A8|MRU#Tj%!ipA{dMu;7(YA zK{-NzW1b0Aba5jDWr!g=&~GQ^NQ|Nyw{P}>MA)k_9$c5`!<&^Iyv|^bhISCIkI$0c z4-dqPp1u~;Cc?QrFI*EJ{D(uz(7qZlQzc`z*gHlH>z$+aL4H1gifho`Wnk9dW0sFC zHI@>>cQ|`7sy=46T+OY!l4x;`GN^`D7w>(>bYED zHqkp>oYKW*bo=Du2e%hebMgDuVbIiM@u)G#QXZ{_D>I_a3QF8}^n`VC4bb$FJG6R| zzN&4_kmYgt$@!CPD-L1i}{7-E&~1)fYwD_G3Ren}1UyI8FE+mqo@`6nu* z+}psHm_%N#;l50!twdEy;tc&b`poUDD(tw$Vuom!C%<0r?sZ!ViSMr4I3uKN?vOaX zC&BFOH`}ePqy3|H{q@D*x}QCMqPBM32FiXUTKI6G`3$EmmzQ|+zOQXdlkL{FZ*axq zvb>#k|Fo=?U=(0`1^slXF;6dHCnz5>JgVR6kUIxQ-{glJCMQvP<_*`{ibh-7gpD?C zK$bJPg&;OEfxvi@dtr$d9-LtH{B+A?))~cuSf;xwbX!ChBn|PgJ>eBgaZMjYF~4&N z{HEOgN}jWYrAYdTu73ZkaGXV4WrlIn7OB4FjFl$nrwbpi1*8iXU(yI1EWy$B=SE>o z3vIbK7-SK>Y`0p6wu~$aCU=^|vuR7hhE~g$mBf_5!qVyUeb@+ncvRj8_v{b?WFw&U zDZ0F_&@a->~xvQz&uHWAUNWyyPO%;>?Lq#Xp$A}h@m+-*&EGk-f{9GFY8TC zG@3t{Nfi4p3dxzid9d7Nt*l%$uEmvo^)kge{<;x@P zfAe*7`SMM?X^5F4^F zm3QPu@e6QftF{nyl{ZuL;0*32HU1ILCq|obMwhe3U|i?ZL?OHS;jP5Wshp|eaklwk z0<%O-tDSAijXfv~Vl5C-Tx{bD);4yh!l9U>0c^_b7z}QBA7+QYE)jP8WIRHssu4Ec z@8LIpTEV662@Y=%0}`eMf4t#F#B0`$h;{hb>tE=aVpIGgR~w#9bx4xUKg1(a>oP*H zR1OQPz;I{?lbfTo3?d|8_8k9X958^B6(;Zz<$qxDkX@hhKA8`uZyrn^K3IEvT0fb- zceh5_IjJ050Q8iKG4Tb(+8NHAnW5W#kjzPcZ#e9q_lCH{WvGeBOV1yxZy1)yjUum;|Gr(Oc`q~o*r_-)W;=|Uh(MGb<`X>_X-!O zv_-CoM)mg$=8?58#@Tn-_ZeIcvgWv%z3j9ys1Mn-$}Z`J#lcqk5k?K zC3nX{?nPL2U^D^TcMyQ)7{-}P2kPVWbKeoFbL^sZ<-(06gvzic5g{XKa?q%`oLkNK zZ2kF{^=D5E(trAYy?_7?|e4I0MIi!X-?lN_24}KPUN0jUI&1JO31n!w+zID&tjD{8>-`NJjXR%S}J084i6E zf?i5x1l8;$JI!#OlAUK4h;8$q*}H6*U1pd#C$SE|*(UqD`_qj;|Isr9V& z9-%n1E+B$kWnuY)&&PtcAn&T!p&zw3E4YZuatOuQr@CqMpJ&gWvT@i_A{72## zLWcQ3^5SnI+z<`1X_fr+1R^*uv5M`!ZnpWH@urk1G;J}1vE^3dG&>>hd~9!ZAXQ<* z9>RLZ%Q{%&47v|rzw0qJ?6{f8(9GAk8mwX0f2&WOwB1%@KfuuuGts4AHkuP!_?0>Pw-c_w(!Z3PvhCcPh?1Ptl%1UB>n1Ky97rbxH76%zJw+U= zTTry5*a-J`r(>MSQy-FsU@q{jd%6rt*9_%Qw3WLr9ugy4i?*%{6_p8$EpC;8zw{bs zn+RNgTC08r4UOA(Z*MVVv5-?3|D_0{j7Jj3(e^x^PAKKhG8E0>N+G5`j-L=pzd0Q4 zo#`eWai4GCyEcQB>VycU-^`pZW!TD3Y>U{rLTCZ8fn_-0!o(zx?8FrySfz1i&c&HN z!p-@h^G-K^4~hqi(w5-#Ci#B!lX7;i&G=N&hUBJ*)Q$q#L+u#d@KLYOg&!XMq#Bi$Rgqs- z?lH<~8TlJD82OaOpZFpc#=fl3mnf7BZdfB+I|eVQwws#y%Y0ku1eH?m=?yA`3Onm8T+fo%?6y z<^aA8e!*7@8F@meAW+Zo6 zDeI`aPnR7pVm!6REmX(~s~1`Pa9M$&VY7}6fg4KX4&->F-^S$%1qS52h~fbmGn;(4 zRo>vrAi=>g2O7DQA+&6D)PW?*brlkp<~ENmQ&_T@4~`e+V+x@1YQ zzRPcyVWL!P=!eU4;!*SL%hZGD7z2?#`REaD^s2UNYu;-THJuI9Ws()X*#Ymwp zhH20+TWi@$??dloswwVZc)(Di+A5e{1%HLMrq!hKcjnK?#QXNofv}{z@>udG(wQ6v1NDImvT}FAl0FveC z%qi1k5seqkPHS^-1Uosl1Wd_xItKy)Tpcoz9fudB@GJsb#q%J#k+t}@R7o6}zrv-q z%^k467q?s{Nxg8!?u$oYWx>TfOAr<(NW4LGMgZGN&JC=Q`z}Fh0nLcOEUnyt2nv~+ zW!(g)0QrOEE90@${F9aGRJ>vMFb7{R=vmj5{&FS(IkJB9U~R+Ia!tc;TgR=ya^uVj z9k)ivhjLA%{iBiLuNCW}nf2LSjuo<7AO28aGW@H4{}g@&u1Fh4_Ykiq@3z4U_!#pv z%G@1a_FsT|)o95hl|_UhC@BmM6z3p!l{M^&JEu$BAAi^Zi{Ql?#Q7N}JyC|CA&eq0 zO*o!LeG*m|eIM(+lvl#L+#qJ<+hxdE*?S`_cDQ&%`EbJB>eTEWwi|`JfZXIg?kV0z zfl(aBNkK`%E(~w=uJMpAsK0&$|CWRP>CxoH;GFAIYO9(mNbX`f`icgJM@L${xo**a!yDsmjQ#sPnMi;fM0^5hRFBDeA>S1Yl0n8eA z!%sp&o;xAZo3JU@w$sRJF2lO^*_Uf~neL=9&q+C2$_1*b=Z?ua?XYyLp)~sezverT zT+#=}t#Kkovux4>r;;Xl(%xN~D~KcVvTG-t48KmTP4X1QcWKKDQ`-_C$YoG0gQ8}+ zrAnIRNoby&ICj+{9nF=te}#=e-0(0$2;9 z-48Gj*gK30A=kr))Vl*I{d&A59FM0d93O!rcae*;fvhrsiO2Vt{oAeABm>F=3$c z>kn?7aGlXUWY=UX!Y6Vg9e*e@^eMX2N;sX z69ecruu;xE0H;c>YprwKe0^BAuR6-yi4|JCtyD`chs{Fw0glBT>AK~xQRYsd+fI)+ z$aQB45K_GZK{`NZXL(3U-}eADIs&i~MlyfZCIj$(=6+oFT5UGCy-}C~2T-jfcg5DL zY5!GZQvukM2~24Ybvb*>U7?G$3WgpBPQOPMyY>z5V!n8v6)# zIA4g}-FRX)iC~xuRoS)XLG>wS`%m7Y9Ww66HW-uetz+?zqbO;dieKAh=y?84Qjx}E&EcR&15AXAy4Z9?OPSR)Dl$rqnbq{Di-tbCaw0Ef(e}ndG!8()V7BTd z))W(1#(=P&mIK~6w{_=DON&8zM5k+H6AonQ%NAlR0CiNHidX6CYhLeR%c9eznU!4Z zo61;?m^5`UTWx1>UW3&gX>h7ZH^t7(s+P8bjVei|Do|>XbC#@K9An`U2>r2Zt#4b0 z-A=tVI)|S_=i=u26i-vyC`-!m*6{3HHJktq+WZ^;E$CUI9;vEWcYHOw>ei$;y|5}r zBN>w<9Zj|wj7@m#X30R9JExjbTu03PqVv%B{2KT3m7GQC);aSBB?`^Vm7qr{lb9=% zItA$F(MbM%$qBD{ZUV;+j1_-28Gi=2F4#hfP<8jBe-$=wOAD7K5DTDAuDZIC%jao} zNCCCKdCp%b2tI8>A}YzmxFnhRpEduIKXns9yTi2|sWejc5dvLNqS$ufx|}7RYuD|U z-RS_g?v00n1pXBJ#OH-AJ1i0ka!Zd0lxsw*CXQwn1+XK?4BhyA{W*M|Vr#Rs=*%?n zpj`*0mNT6wWi%Aq@|QOtqDHM>n6#nZm$zw+50Iigmce`3K6-UrFj(ivFvdDxX58GH zf@X0EV^zEcq#>=cDXTKpu)wChF36p8%H!-F2Ey{hnipOvCivBqg-M_U?zYI_^ll;o z_?*!j`*-eYo-jzie88NPI}2y_-LS{aVq7+_K{z~tR9frlhb}*(G`V}LLyflUx<)7m zn6-(AZ2KT<*riYU_PT8SjjSC6^iU3(EhM!^dLSijnFbjqZu3nO5hUN^bVhZ=4efzr z4t^I&JQZdqaDXkhjg?m7S~_T-BAZiqJ_rh3E3$u{3BGl_-|p1N9kNGSPa28OEv;2K zY~z|d3TK+%fnuWaa;&*baewNWFC48AYk7#K z+qlAON$K{2((OK#dM$i zAxHL?Q--D`YHP;Y@@Gx{wi&G0Cbz3g7$<`6`MjmQPi@-3-t45r7#z~s^RHwM&CaRR zKQQ;zInoH4nAEfFv20scA%=s6(OKFY$fUd`>Xeq_drFOhdSZa1dLj_c7-$1r`Es00 zxVOjcYMEnhR^4vBc=fV6$3eG<;)J@PDmW1qr=wYyY+9qE;@QeYT;G?PtzwiGpQC8x z%NNy+s6?_l%rFtX>c^6sZ{8%P`5O49;{^*jx$#O@-gDqj%gJ-CEX~QG|BzG4e?#Z= zk7L9{U|JdGyJ6U=OH9V6cw`iOQstoK8%I$Onkzl57KCrUk*FUjT2EmkL<31_Z9!%s zEgW^613>4jZ(QeLd+vqhX7VmqwX2H>&nKuZft4m#C`;gQgT4Ta!r(nq_i`w=YJJ%% zA-IC>mE09g^wS+fps#-89GdWg?BOGmWKQ{;UoVdR05gR8@i=V@Bjw8zqJlvabDT<# z#8-K=AxLI-B2j1TEt&=C95%ZaSrVtt*r4AX zaX&M6FQY@h+L#9khsI07x$aHhjVEt&mYOb7kC*r%)1)_b}?Ojhy9p=DMUOeCFpyqmsc0cu+?25`{i$0b?as_f!U^V zY;J>C;An8;rv{L8Z@7NW(13AJnJnWTjtT)^I!f7&Nbb0SU;A+7)l1AUT_2N?w?=UI z#^wik3OC|Gazv;T%=YKi)$>Ue4W?X44S-IXV>{Adu3zxnemgFd#Qy5-xb}-{9R7&x z1z_TuKulFI0}wC{+NSRW5*)>~fOAryfSlCNQBHRhB#8xFlfpRRm6>g0S6A#u!~Ui1 zUAkl6cT(qr803Xm2T|;c@eo0wY9w{NpboDopHA(J7m5B>*PC2NI&{1_==~Xwnyl5I zKKaP?q>N?8%5iE>(8VwMBU?|oUL^}y{L8StDtYk$n^fg!fhBo8S~>UJWg5Es`k2Of z=q=VCn>$WS(wEps^#uE>7`%meR9_}HwPG5nGK+5#8(6jlqKyYVW3|Uc+*;#;Scc}! zD{U6xV>%p#iq@n=Vo9_mxg`;(Q?;A;I(JUZFm~oNxpWmofv%!fB-RxX&!Hy%BRSaq zs;o7_s|@fNdy8nWZX7i+Nb{;l{g@G@R)f!pqHGDYiSCy03RCJ{jY{m=k%7K6ztqrQ zTGAWu=QZaGv6u4-M%MV!*2HLUe3kYtXPldCZsAoG9_hQ)sw-=Ookr#^rVDihrNRkM za;c0!GHx@jAgRg6+ZnkLr*V9i%RY`2lJ-cPAxcb zfn1ze74v8#P!VLW92pQBl4nseaEg#K^y=#I=2cvAVHwZYJpfr)iE#8~imQjz%JxPT0*@-9(#?quiR3?u8T|Fr?T*4D3x$)N&Yb z43TYrIw>cF_Xa0l>VXTpvJE|~_a%rSU8VnIWI@p(t037%&YLH{(1N7Zjk=pMZ18fk z^XX-c-s(eYI4Z`{wmUZQ~s_+OV<}J*Wfr52T>GA+2X5w5#oWsHl zt8^Ck{t%qB26J(V4gcPT>P^)!k-pP$pNmbfJRL2F*#8q7+g zlE3WvWA(xG$Leb34{-aI6Kr;5b;Sc+4!-R<0N=O8{_8QPPs%Dk=B{AlrlmG6Bgbvd zrCYwdHC~_E*H{1G@QM)--;J%K!*2U%f4_;hJK&f-efOFnkNc1z4^j ze_2HROgH0$%XHeSSUqZilmi6X?C4Ji<{@{S-Py=&GZ%)Xl(V&je~=Yj=4{}q#D*@I z5bvM`K1JH2#Wt~LkT<4J0;?=5(kx}LK@g`1-u5pH6IlIm(+8jRKUE>Cz zS)c9AObHMy4LM^yY+JO>!7%sa(evj|o&t~27LC-%;+qS3f?CSmFy@@c3loNc`}ZHN zWc73JU9JPo@z#n>Vg-YHu(*kzR%}>z);DY1XeEILzc#qvAM4w8G!#Jl4Bfp-%H~b| z)-uh?u)8O-lruzlJDV*iMe-Z?J<#=4X2_YM7kReMI-phJgIkWuBIA{LMbnnTd-y^Y z%H_O*6yW=#p*|;Fj~t2|lzj$4klt%1y@yr^yMEhP`wv#tA1udTRu4$H?wL`wA@53^ z_Rnkjz|_fjYOm0K+dtXHHEn{+2x|2UC2pi2;^kAxD5SBR=MB=1^l`aHShN5U%@@+7 zAezYyy}`cP7->8JZzE5yWjdd=2OIICV$RIiY&aHf-SlS$|V!g6Wh!sO0|$RCh4sX4!)a*k+DAZ+J{%*tQOOxXp|yN%Dm zPX-8-I*O^ieQP4P5CQN|*lC7xxN*>8e>?V?3u$?4e z^7Y3R+csmx83ha-}=pG77LZsg#M43U78YIMAj{>j*BBSlXBH z43}qz^j0jc$g=h#9Ry=XO|CrgkSj@tOM4Ix3Nvb8&O z*nBp^F+#kfbGMT7TRCuTE=)N`aCekRTl{aP*~8R4zx!H7Yo$6U7MuJ>QP)udiRA0uj$Wl za@ts>Gs{E1Pj>aglk(zH0vbC?e900|Vt|4zN(Q-q5PZa=UOb)MSE(fKG=^wZhb1(- zyetx)5jh!;C#Pr~k@e)~Y%Sk8e?WXz@vu!U_T7i8OzXR1uq~TxUCQ^w)BI2NO~7!9D(W-Cn6NLB76Ns`^xK|b(g0{nfaeT%FG zd}7KOno4qA$OHLsl&)F01d3YU#UJ162TnLnZu)!%*MHwN0+@nuz0j}I8$1Hxd!}i+ z6O-Oq0<1M}$2}X(E)YN$^EKGcxf}j0g)}VwBbqW@MNQ&k)rG`e+MYW3P-FsP+m?G& zN0%Y}#e4{yd@HopzQFoNXRTs=f>p0q^J7|(wXez08|6pXq}25)v}mUxsMfzkD6S|1 zmY@;Aqy+!aFU>%*nG5z6)mSoS7G{{>e%&BjQMZ-}{AI2K?9>v)HPqIE2wr!M$K{qa zm%)BMzTK=~LOz|Fx+iWAL`u!;tTA}c9lW0eh?rNHYc8QIwxiPmN?3}k7QA*COjjb7 z+P~5rwBr2kU4B?KiQAV`(3=V+xNr#^(}@-hGvXW*gnqi2!m=*^7PV$g7fU!aR9Weh zpoe^abl7?$ryMXBF)jg_RD~?bj%b|(@y!T5iN$!{JSd=ui-|&#w67|PYWZcM($L#G zVZ-x9rw=8qxx_}z{SvxbT@V5+rY#+l=irP+0%R=_G@}Ga#DyIc<+*nnvS;c}s#u@M z!$sK>Mtwh!`5tSBNu`-swh2pk;}`TKW`$hT{1$u%9N#PzhkLfk==$ zbfs88nWfi)*H~niE6d$)Pw;~%emXbkUED{f>u__*pmnyQ#s0_9Fr@B2b+>%SzLQap>+oBKynih-#Mnrs4% z8gxS_f0tdWCHgAw^$E55P35T&D|U;^LjAM>p5j4^*2|IKHM&qakUK{f@?er{i!ojn zMHPx6+&vmlT$|zwN-mgIBv-6?(|`5kGO{X0^(nI8Pf$|ba_sUzS#6xSG?$J43>M8cED4Q(YQRR;Ule9h0bXa`-eF7sfA- zX-#0E?N1q~%3Es9NX*qH_>R2bAG!Hfbd?-q_|^X=b?j^XC!WpySM}`syPm7eT+U^O zXyj`WG)LX|I5U0X0N2nXkG@ZK%Bk!XUJ;K{4@dZWu_v47{cAlTaGY3=B({!@+S_}F z&2EcEVBSt5h#LRM>7|qs0&+xec63nq77sN*|JSg6i78DA>t%emkRsP6$+d%ztuf5o z-mgus;p7&CTqLR>{JZ#sQ4;9w5B`!BtEY)>P69+DUq#4daDH)p;#x~lBaWNu8smC{ z(8e;(5~$8wi%Z+dah|M3c)^puOi9~}j1juDYlc zvLJHNWxJBActVNVNEuU`4OVy_j)&&eRUFFMe`9^a`9_Hkn+l(jfOVu!&#e`>oEF9L z%&0T}CPCZ^hsA4kT>sZ$Z7t!J8Jz-i|5*%AAm_c8xJZmk5pJ;hb_iCwAPO(cgy=Uo z5w`sd(Gc^|lx+=-Whpjv66Ei) zG2KdEAKC2>2PfkzxVgTX^3AM(!WsG)81aYRtxk@j>4{*;P2!AduvEo$9w}-URH7X! zuEhy4FAk?E)x*hkr`3IN9r0kH09yk%qEN#?#S(a>{2Tz;6it>W)GJ2hGC-xaCUU}p? z46iPjm>mQM4nkiXAtpG4u`_JV2xs@Hybg_<#M+|lV3E!al!moaU2+D~vfZ6b zBKUc6?-}RhqDJ5eB2s#xun}|=V=dKDXG9+(CxlRlMxAcl2?Uw$qz-8D=BTi- z|3s0sf~@;(qiW@KW=N+rL`INjf&1(sCb_r2MHZqmtr~N~Yn{}qJpjEh6NVfdG1Im1 z>5MZ%HNJffJ_`xwgkhQa@D1-Xk#d}3XflfXIHf&H;9}LaYmCcZVWQ)q|5)y_m%;qi zE|U?#75s0R%Vdr=KIS5sOsHV-pX~CO8{DReM9?YJ&6M^tO%(mxQ9&7v@2V8w;rtXN zMR)UwYOj}rDIciB&d~F%KrZ(s?>G*K?h#e$sw=1d)}G5HJrH5|s>3a>y5*k=Wr1p( zEvl2eXCKwkL>L(Dl9g!Co2;vG)wHOfDcdj&X(9-)bNnK#DGdAQ#;LwP`inbd*M+5Z zUE1GlS+PA(k`&o2tWNb0NU+?Aqd7f~gE=`i6YZ>$;6(-ZRDnJ}2wH>$dag8`FBnwB zWv*fc6lFG0MqHyEuF`=$3FjQR+XW|N8KD{8NlM8O?$It<0aD$iFG0+_=u5hudFv5z z&U`Skn6t_?UozG*rs@{kuM>eRmMC0|YD%Z*DGfm8sK6Aj2&tNK-}M>W`gMImi{M5c z&O~O0i(ZN?#T{pPQ?A~TcUsRDs`YIb_=SySauq!5j)VCd-7rw9YbmR+B#~6ztc2F|{GeIO zVKSz%B)XMMLKHBI;C#3&er(<`Sk^9Qc9~FFUPtgN5v#Fjk#R)2IITcFV7Ie(JN+cf zMlKdZ&rFz|$mNQpT+GR>l$?GD=aBMq<%l#Mizx7PUEl(RQw~np2qTy)JvLIQWwmSC zNPr>jS!UWEbO_)3Ebf69=l#p8A-1?G(PSPW&bpQg@^wG!h*fdjuU|1v>9irBrfOA^ zO0=WiZJX!-+s&vj33}IATcB)HwkA7VS(z|eb<*o5Ou8KA+$mqO!1cxp%jUH`cfzDe*Q}gtpvTuB z`V+A}YN_eXTc=}b0&HfJfju67e0jNhuE{@-F2`HtrNmpYRV zw?jn2b_nPX`*`LeTxpRZqG2toUmJFnM?e^#>_MctTi798Ht7Wdf2Y|# z_Gz+6`;BUU1WNkb3X9a6!>fzlTiho4WrY!atIgqA54pUfR(&z)y&v-f+F*6-qBj{1 z;C{QpyJyvH-mvBeZkAPBCl^uoI*=TeP zAFw(1hw{Zf?16y@>~;3yf83ziYpJ)+j(js1N@MHn#{S?^OR^gWgLl%ub@t;BHx(}5 z_9p_BeL0$9%PU{(&2jHaE3z+tyy?H~pD=HOy=mk1bZKIPz3B}4lZlkwV0XH>!mXL? z46J`ttE4LX^X=f|8n46=5_|L-ehKgTxAM(SeK+ap2Rrm9Mu(FQZ?H$-gQ!u^<^^&Z z#`}YfF4;iu=A<{hnev*`At>;)hr=-ZZXBwIm{X%y@2O^2vEWzn79@ST1TYKJ}>2fZtnwZ80aHII%jutVsNakO?D z_lOkkJ7tvV`=yz2$22ACn* zN;OWekI!)-!gHmy`t#>t7|ql713uwyp;wa1X;!ZOqx#w!>klSer%63c%9UTMfB7ZL zJGPM2aEZk_&sZk~e_bf`cpas-hU1&l7nrgbvQI43dA8x|Z1o32ith+-Qq9r}HNW(& zOusq`;Mp`Y-$<W`)5I9WnF;m|Jd^R4D(1aChM(H9qb^mL-%B zCFkl{&v(HZb5v(Zx$@{g`?WX1yJ6F#vjY(O%>+C@P-`wLS0C+>v&}$rl$@)FRl@O6 zy6cH*++;{C)RLK%dpb!4oTW?)WuK78@AuD=IplY-Bv;Tu{~RHBCf9$7A^XHaooB?` zft&`X736$4FK?tTS{f3vgQ#AVT>;CvtXylrvh)zLEm_(JOvshR+M;Yi)=+iO@@Xy0 z&p}#8L@a?cN*T(*013IWXq7!YT#E;F_;9UIOvw6hJuS9gC?-D8Gv(iCpI3^l@^TFD{9NI-*1I zFde(h0;1#eTutN9otx2QFzu)6olnfwfhI(a4cSwi03<_qX~i1kuQW-}!}|udgJ=$- zTEswxBy-@QR=bip6`{SI1^NEEz&&l1G%^g(I zKT9jrct#1;rRl=Q!A|NWIl0=HElQDZ2h+jZ!4UiN2#c18U3;z)-uPgp*Ra`K^e4x7 zfCEBq)Qjup^KuYOU4|3UshH`aR$W@IhOzy<{Z8p1E|Jy#RUgM{?$QM0qvTvY5ESdRJ1-7P>P5-LdSqvLqUU$6Bq)K*!gk3AxX{o$r`oX6 zI&7@`va);hs@-W+v6v@_j=9p^J801&qhE(dTl+_^-~_*O1RD@Hm3ZhDJ0L*74GFJG zYY->5xdN1{R{o#t#TQz{B@GigLKwY74@SI(FIlM5z7^N#O zUv;{Bhc6q|bKM{y>D#@|-ZuxdF$wDdmtjZW(fGuyP9dvvfUI_N4;Y_7bmF7i=>iiC z9103=#@V>ot~!TDuN&1V#2w0XI*7E3GW>J}c8<`KvoS0tE8FN;qk4*7;p2F>b$E2p zI&5Oh%G)Yej=3RQrj7A908VL#0SeITtb!2dG&67ns_w_dchJJ@@@Lp|p>(%(aNKG) zyRX_H*GPULYrFOO_-OACP_PB9h&>;v&3%$K=!{lQ&Ma6HlefBiAl6sFk{XCA6S2J^ zHXYm%C@6iQ{pKN70#K0_q#yVD9_^R=Eq0wd3VgKpUhd*cR1to@{?mTz@MU*b&SvSE ztEOdSNv}W;9uqsQ7HZl+Tl=kVVXHkt2mJ;YmAXf-TkV~8>yH>c1r`X--Z@7mxw~ZH zN0M>$3J3s0#=0bHuHa{qzjt_O`NIAS`DC2}7(bGVmeD0$e_J`Kq`AsBF?9LiLggWh z*LRh+m_&uo5j9|gE*s+zwuxl^8znclLGhS&+Ur12xLPv6;S8Q@19KX|q>q@tx8s}2 zMelvWr2w#lWegO6yj4-FTyn>RjP2ITcB_TXpQ`1fVullB?KF4Wt;5~c9_Ga52Ezyn z>>Y0H?t&}^vT3H%nQfbqcW}TQwBj~jY4&bws|(J0!EODOZ0p$>B?yqWqlpTXHXILe zlZb3VB35zlR(-u@RiYj58Dnt5jx#8u9%7zBCzC35@@nq4dD>{GH*=`9<$c4mg(N`eP_Equ$Ng zIqdqCn@i>W?|MTF*-!%+;kdsPzM(hw?Bb5cdqL2Txk;2lpgE2P=$x4jE4HI)`Y z5NHV=O!uvX$$-J=9apjw0)5PtdzR4J{G?(pP|unEyBCW5?Nn^N+}Y zzo1AW0;2RF0h;|Aj=3*PJ}Qxu0fqcT21NCWA|T4lNq}!~1~p_9}cT~`j zZLGeVvih9@tN(`Y9iTi|J%m`S)#Kcba73%W80%LVw?7&DC0k(5Ar*d>(!`SN;FgmOQg>_9x@38`x*rei^qv!AU)`A|1bp15>=NGpY1&cVCzd zF`~-8mNZU(d_!+?qF_26&L|x-`fms5`#-Gch+97q7^Y0ID-pk3lWBpy||L83VM_K>j08h#A z%QgY7fDnV_DgPhzF-<6`0^oQ&6jy;r0POTf|M*{kK2{BXjJI$9@xRV-g^lBjCBarg zY>JMaU=3a(X=&LQ%qo605Jhf=AdtTLeZet=IH}=CW2X&hS8NpJBLN}(QUP%+4jGYrkPU*YWYmd-1Q;=#&POMRno6j{9voHi#xj3X zW5(Q|yq6d|u5blO$Os}Mz!9N=d&79Roc#+jBUc2zRQ<_7d?LKuh>9Q$wPVQuL~6)qne5PPN)l9ZvO^R3g+wPinzC{T}vYCvXsQsg@K0V0J=bDm~^0ebaI=)!|n#&dZ_BW3h6dCKeVnm)|t7L^TGkX(8CY6Wjz1Wa_7iLDmd6zoYA4hk#D#UFXwM=Egp zyT~&+ZpVB40lYZ;$q3FI5*i*I42-evg0Ac}=7-?;_$NAK(IN{FQ?fRgyO z(szs_M7Gt5AtqOKR)6@tOs;#KMnLiIfq2~T%yNp5>=(`xm>D;3aSMIe-!2A)J?Nc} z`Z$e(^8zeDGSg8-HR^7WImy#buJlG1oBp zdN-|!lTt``cQBp1T?7qYVK;qw@1f7rg(xR@<0ataMIbr20Q;Bt9ibgj_VEYT8N!V= z#bYkKD-KK;i3+!lrH%$jA3=spUnZb1!KfJ8T^8RiiQP+ zI2;slqwN|_%*0`Q*P&V{Wy9g~^m1>i+xo5xV_oOy)#0`mWq`Hl9L400s^c zPkdEB!~ZInXIeZpbF)&d*A>X>3R~aX&bkn%p<(F<8#Ey4b%;M0w7qSxpg)8+LlDT? z+I6@fy|f_WZ)@6IQdZgww*m5g$m!f*87YXGjv>Wkf(ln)A;265p|i1p4bI0NugnFr zs|v>oHNSAg9MedOt}6O2S3?DAMOYReZn@xvod?iJg#r5}fx?A7Z<7O&ecbX2xQG(x z8*L+i7jNV*?K1s*f;LKH`?#)(H#Tm(0EoU%8h15;y-atL<4$PGHA)-A=fy1)i9@WP z4O$hs%Y)DuX-o~tz)2;G|1AisoaeV>FGku7ujN_?Ca4oP^D_!Hw2%umRKJ`~rhg2= zS-&ZjX4LnM@b1gP=~WZIildsI2-viWR`2}VJ#D%oLiyMT_RrL{Te@rUNT77ol4W9* zvg)v^k)f;}WtYD)zOeEntt=8QOq8iIa#c_~^OG@U+Ts%Q9wWpqVi;Zw#t_FI4n?X# zVy$;}U& zF}(#Me?{k<7dBS}HP)HesqP#~He7!2lrC(s9x?(I&&MmS@^soAicVl8Dq6SH5*-6w z*Xgvk-KI{-tl1-75nJ>rC7jXaHGXlwls{6OzsW2u2y|D7GTm`qOG9L}>gYzUD+uGY zWT##LAmYT-st?vSpclG2eu>X}MAXy-;1&D5E4UD!4G{4WcUD9PcSoHDQY4`RpL<+K z1f(uF)DE3ces`glr7;8y&ZJYhJL#f-$5&A~sdaR?ZU93J_m#01@{VRLdTMNnG z&mVm$_P!IUTi+6X z%pMY!W;If~M!o0!A$UDa2IIr2rFqM8WwKa*WvwjK`27Bay7}HjId|v_S0Oj#y(DJ9gV{Q=r|56=;WyT`a3Svctv8$`fjahuWTLO<{dEYmoN8dVU? zwyf$BOH_IEEF;T_H@-N!6n~gY+-wY4>;`GCuCQ=X?FZj0M52LeP+R?J0q#7J&|j(> z>^t=j_$Ka__lSNjxbg{f+MO&4I@Ih5VqnbLF?cQ}SQ3^M%-9xNU zPpKF3zk-&A-BFD;`|cROvX>~+pJZD%L;5>r-;Qw$7O@m2*V)VQ7{^-Q^zmG;2uf@u zP(c~9nK}Ta;XzR@9H2y<#x=t(+>HRep^f+gnrcN(wB?5Nz!$P~EA^iJYDn*V2}!ej zxqHiQ`&_2*x2r2DGBJUV*0-_pH?(X@xP3I`|&Um;dFj=@!{VQ*UoNBdhgqgM!W%l_8S z#i_flGfXPhnX!owGN?&r4oOjb6R8ocnJCMd$;FH)UHs##)+>qh?h))8{3G!>%dRM; zW@O4FNrBU@oT%Ang zGSH%tT4Y}#$+4j2O2F{soJKOeu%h8GI~kBs9e{@qXhf9+pO;m6Y^D{0uu}>T z-)XZUJ=bo?C}}ceYMYLeCr=RM2uO~;J!;*H2!V3w@(-7VgWbPW4!pxig9mxCDK+Pl z0?xBA{Lm{b2ygB9=swjS!TCSeE7)x3_bQ!J?RDBeB>-7Wv@|Ml1e&eOSs^x&*fY1A z+j~brl+A~oe}P#j*`J4|YfT`9hA6c?%(IpF7a08EizThG?&h;HW@e#!GOFQ8$-0hrs)Q&Vk1 zpmQ8-aCzgwf#8!=dx*D}2r=E#21Tmmy#WDIqDh&!oL)jhvG{;wS`a=Ah;PEtds?s% z;$Bn_zi3vy3gvSt(v=}A)X7al-Yv>StP-Nl@Pon79Ieu-1ChA62 z`txt39>}PQDuLW|+LTK!ko6*GDXY$G0?Vbl3io9oI@z7Tlt~L}gEw*qwIc|wGMO#) zV|gt-;UB2#=GtM$ySTZ6?GsHB99`|fbTzA}odMstGHW&x{?>gnJL;)K8scVFOfRdY z88kkWWtcUiEX&z4%JOvcZR*p!akae@lZ*^|r1yRN!Wn1UeZK@mLC*ZAu84?}#GYPk zSoSndJ;G{(nC-tA3dI*=}TpG@v| zhj=$=aQ1td$e?W0Rjq5p27`F`IU*9(s3LVLi1mR`6nJLr3YW8~P4ki)Ztf$ft&OD6 zzVxcQp_>KS+RuTH>s!|vhEjshPqkS-;a5HgYTDpjULGWTJ$(r$-n2*6N|}Bb#FCSx ztWzT0CR_Tv#%eqk;m;UeM#{4Y-I#VI%?pX2&-x+X9Y4eOffbz~!Kq+L8IYo*j`$Qd z7A5-)giTso`|^qPr`>o~f3*Ji@t5{}-gsVrw)UmZKWL!%#Gf7TwJe|GRit^L_P z=1DR8$Hws&NwH38v1X&zUHf8x^|M;-_=}%Wq*i-KFkjRF5pJvxp*t+3N1Ggd`KH;> ze=%PD!H`7u3n$z9>)55pzm<#Q9v-Q9P~Si}QU#VZv+mkMMNEd7&47gL_zS+U_DA`v zLI4#v2BY2cnk3TgmNP6LLgI=G#%yIe{xj@I3g`up{nU#;g3dWfDKx;~xHTjx@O=8{ z!m|y}e(LINQ3gzUTLMhe1(+>Qn@3T zhH5uD()h+A9MluQ6OTJC*I}5qfIh(H+G)5S4?|s-8^T*(aiTEzTES;T%y5!Z{z?9o)ysk5B&48)RxH8n9j z&g%oBU%)=Owhl$NM-^dSXRPdoD=Wl47q~E32({R~7h-L0Bc*j{x>WhD45eCMT3^U>nV)^RhX0!i?*Kkj@_BjCaoxF3bL}G1#J`soJ%SX6Igk>2NmUWxiV7|D4865c<<)I79}8yD5r@NApDb@wT{;=e$+aEN z5uP?aYKU#p#R31pJN{W$S8?gfr0}D>08oS_yAVloS5P*)g(`%)4%I)>{9Qv>`YP>9 zD{Yii0s?1OB0ef{uMv0TTgStU+;EVT8x5}6 z&m0U&+gSK(&QMB$L?g)qDvskiiiYS16D!E75Ptr|QNU^cg9O!qYoL##_>gb!D%-+_ z)j4jie(}UlfjEg8p9}R2OLjT9JC2-7Bj|jA*VMi? zQ|49ljk;P``-tb3;c7KQBO*Ro2D9GV8^oT0HwmJLA>2BiFy>w!&cIHF9O%Pk7Cubn z1Io(jD*mlsUkpy(jj(V1%I0F@iDD9&^kNnM4O%B`spN}&t@vV$=I$6?$?&SKt-}#S zZ`!cDudnMgbo>&5N5dEAkzEo?g8r_D+^@neom)+N##r zJmZd`dMv=!*X%Z!l#mWQ7R1l3w0@sT8~3U7s8H$Y;_f}ZPo*dKsr2Q2Dm}eVrDyl4 z^!y%`Hi{In808y9ig>O4=l9cEs=X~jL;%n3eFXSruoN0&K;k7DD??!bvSg3T@ymVE7NcnM zF3eW=5=)FaDM>2cI0|nPnJy^P(ZN;CS>~cBwqMQ~fkmjBFTazK?omhly762S=k>-# z@>Y_X*9R9NFxLU<|Bh{Gt4|lViqky=o?YCgMj%|+;^W0F`WWPk8}$JW7q+{;xLrgy zU)-XPnpo1J)+T++&Q?f8RzCO1^JeyNytrW(jWORYSY&WV0bNSmCvHUj@BFrqb|&sn z&0sEP{g8-qOGYxd4xCGDxLFVtw;fM&2k%MiT~>epprn+(z-A>^=9gq&ua*FcW^j{! zc)CQE9$b~Q0edpFMIFbU9=KgSj>{y!HRRP3G*CBc^}3uG016t-q`zK z`o#l#gjVEI+&p8e6V#F-x3DTw&vU7Jg6mz|w;v z5~3)eIYc;E1>vi9&j@xVHeYM=O$4#wU{HITiP6mjhY?4QZXUxe4lg*{{HoPE^59)G z5n`rx_If`j(rW;IYHve(+$&}sPVHaN(trFo;%x!z#hlez`5rd=Y9_|>xkFFJAki!~ z5@Zm9PyvSTZ2gh@NG(DZo5Wx?A}o=~EF48VGdx$|xeYJuD)UwK#%$cP zw9KlB-Tm&w0Ry@|{rOS8x>{()mMdlZoe8e>Im#hls5DV7-Ea#27$) zyscBgbL_Lp_;S`{IEV+G<)*{I%UnhIA;lVaXD9XkWk;h1P9>FJiU? zX%h$xN+)7EV!}*OrRMkJzCBB5oPuO9x0<+e1h!H>ar4@>moEc|WqE#Kc)Cn@urZ5N zHJWygAe=PJX$gdrg>#N%Ni1rEkMKzMq)RxvU%CyY{kzMOZ}t2Q$8UEh#CVW*CY$g| z=du*jp-a8JmUqWl_h`gU!gkW7S3?s z%%O|5&|^=zckZ#TIlsqCV9hU(d-dTySSwqDb1|t~5<~mKod{2!!W5D3EyW=Hit;)o z4{6mFS>Pt9T$n^6vZ>M^|7wBMHvA z5p~QZ|80GgGPsCZ+Y48MRWzs3(Bq}ngU$i`Jm)O|=PSAt{X3sgk7RGhNw30!bUpD~ z`-R`vRIRfhbcjdl<3^%WI~T?uPe~WWL@1a6HDNOGO$ljQ0~0xEsK~ z7In*|%UQ~oe-&&7aYvWd9KXjszi{9%mpRu#8ob_D&7=<*?y=GDo&V73U3l|s0GkyQ z#6}NLvI?%Q>Cf2!Gn5hV_(#A;D?!*-Y#qGcU~nPwla6H;yoE!#+Z^!r<0tL+On{{w zH!k2%-u7XWWn1MpYacN^IOzka;j*?M)RB}?o!w7eNN)nfi0;%VZZ5>k?zsYT%IN%} z`bbXKbU~|&tj=RCrG$A55332%kyZ)xb{ToD%T<`29Ix$dHII%j@V;h$JRG0jmV`sr zWBqxZKnUo;WqdTZY)9;t%zeha)55r)+JWq%?V2)?e}+LvY|Jg4h#SvUrlPR$M`soo z9ys8h()8g1L!1k5mydTJ%j4Y~OfIhH3iI`5g-9^)3i z*~F7up>a99Z-cyY=xB4JGXJT+W^rttoa6dzO$L@ zlNlNFO~_SR9rYdCke562jbP>$`X+E%L8{5k+PmT%)17NN^uC}$7jSO3mL1NcOU%C( z&XXoSJ|Orocea`Hr&!Gg_QvX~rxqkH5De&Ik`8ksB+H*ILocV zV)S|dsOwk4v0~lE3YT~iYmN$N?UI;$vt>MoU0oXr#y5+q_4YzYnQ|3nI<}Su%sGo9A9YubJk(ObHofnE%D`Hocx$>cvuRrgvXrWLU0IXE0>xnQ#yK z=e=D#QTLZSYMqoxiZi@ZBxFIo^04<_?ARKKkgpgeI(9BjlM>}{8ZQIXxkhkQ8DYs) zf&I=MsSMH17;?1A=ac}L|Aa@3qyu-SIlK@xi{fUI5d+ob+W!9wJuSr`6>)e}Xa+6$ zx1kmi6iUVfh0i$zb@Zy+YU2T{JMb+}Q3{!l7=1KF9r67wbmLxRmFvKNla=0P69Kja zS?SYVpO&ol4iEns$Z8KSjQuxR-A(!}#jTt=hnMzZUW3m$yarG8l?X4TP}aPjC;6;$ zx4Y!!kSY(4Jgd@da3}1o#veh)}(>IY>$UR zi!U%MMbBIa*lgBI>qYul@AUngNoj85LQ_6?>HT7-N66o+uq#BtE)-P@SjL>zC7ADg@k-@^VQWU9z{8joY`0o@^~~3UX^BgtXw%qf?zUTpyRE(Q zi4uk~=&%00QSFa3PW+6H)E=CEzFK=w$A9=?_w4I+-y1rXJU|GsNVd#?^dG2Psq>P) z@t`W13AR(4yk5yleyBGS4cXa2F4rqV+@D-9pg@h~AQ_%J$18b;wh;P5N~N#Iv7iWM zcM%+|cL59`iT|u8wna93G-|!?kFI%($33=HlN7!lEqu{`30L%A_2XghBzf1$PTCr| zB0SH}CGlOP;=9%{pGCwO|IlhvL*_T4lRTdnMUBtv6~ej`MqcMrtDIfY)^BZXtb3 z&@i5y4o31O8D#b)0*6r!EB(8w1-ixY52ob5@=dVlHLUcN(vY#_YoVml4r%j=fi5lkk z#TuATG^us0#Xc7Q$dc*|er6|I^}$r$6VX<*l*P>wI7Qw=I_sT}`iQ6A7Ka;oM}>i5 zyaXDYoiUg>1HctS%Tx!@kSpIBjz311$yq0FU6#fAPdAVy zv}hOzSo<)h@8O5u?WSzzst>k5*G-(GnGd%g#^of_RzJ3OtpIf2!Bk~*)j|>u{oW90 zYnPXqpbVJ;JUbJXAdi2OSTwVpWD2f~19{%ysXs+Z68lR6!DYA+)}OqGF~V6tGQKlu z3_7-u=B*KYAs{>lE5O1tu!O-eGg{`XZR8tw(_yR%D1} z7FMGaX}pntY~I~C^Lhgm+FP0sfv zEFR&D-CA{Lr(#)r#>rBDF(m3ge+%gW3(+$nQGSGr@H5J1Pw-!qA_M22C{2U)f`Iv8 z3et^TX>XQ$W~GFy%?#cQgTEs5FnJwBUj)f;J4SOmiSJU*TFDm{tJz`lAmDYQ1Ft2k zeSwdSpWINcvkU1Vbd0U;{`Q-#X8Yw);}^uL?MT-s$!Lk7`^GZ(Z6JHf8#MFoOlr*V&fF83R!oc7WF{wa}R)1}eN7tQ; z@rS+9L4S1PnndhpKj?Z3S`kM6ZEJULYroZ~e%r^xH$xd=`{?Mn)2MbQKG_7-yLvEbH97J;4N= zk0-ZTiY-TXT$2sPHs0JDfmiW4N~C0xLhfq2jZN5!e}z8VvaEvw_qHkWCK7Y{VZd!+k~EGq*VG-&}L?yf@;Hs5xCf@;7O~EhcR)B9}(= z8#Ss&6pV(il#&|hXXuVU zHLog3E?1RF^Qw44%+>ShGb@7zg|p@=#-us3?(5#v$fUfsWzMX>)f>g6odXhQ%>;o- zjj28dBF>rzg7PL`)ybiG_2R{ps~>001Od~f$3Lxtr^b0=>^CGMkb0tIM&!LW9YQGf zPtgzTKH?=U^PAj7-;8H2-t77fj9VqPX|WBRSM{TIF@^}*smj)L97YEX%UnDb%&PE@ zXe`Q4;=aldB{|+yJ;b9cb9#%@XoajnTm!Bm_^KtTjiaIE*yVL&T%sO?#y{7B(rUz7 zX3n|Ve?M_29$F{d%nNu6(W0~gYL2N~{7RTY}ii0Mfm*$AkIQmn>5M&csX_-83pfUqL~(HztT#H_XbdT^yD263a5 z+!2JC=trA}Y_=eeV($2vOl?6zu|rO^cbe$l;mZ;QKgxwRit~J2U&$%@FR_`tQg!+j z94kyG$6;cWv5U`;$yvijK-61dIZ-S1Z=nhEHNEOueXafocV%uyxB@&LRq`c83#J6k zq#^mmcH}E_E#6T}O|?U(npZkM-}FvRchO80GcHaq`Oc}1AUvg+p zWKs?LHA(edZTMdyLn^x=X!6O;Wb)xk_vZqDlab%$O)Iqr)78pfD|SRHNRiTpR%_g5 z0BnF;CL7o#$enTdCyhsIs$*`V+=QBv^3JQ?1c#O0WO*!~qU|T@R|&~R6U>7>Hh(j% zNiUKfNy-B9k70>K5m-qN>I$d0?&7-2T0%FXMW^)}jpe``j&lPlK>GVqkkFbSoAZU; zlAB~4fJ=@ue+2whVFexWIm%@4i_nib$BdRWGR6Zdnh~|(Xyh(!yto+*sj(Crk(`oN zB>k>K!M1_=U9lBrpb@3obLUDT>u?31IILyXb)PAt)3$6D_eO0Tm4F#wKs8M(W}Bvq!A&O+bYbVQz7#3>9*5&(h#hw9_EdH=0jn5l?(ZZZm~q4^rz z?s#KC$`Krl7^WQmeYJ8&w9pyi<#4bcqYNBNoV2Z8vVmtPB0JEC4W4MCYn78x5hKDU zrIks#$hD`qt0?#QatseM6=%VXadNE)%a|oZ&NB)G%D4TA*3?Zmgttynw;kI_Yprr) zHPk>Pgoa^_@La*&X?W5OZNR35i3(re-&>X*7QX+xyR;IW7}w%H)6CYbBv$Lq0ZwY2 z6CI6>D{wU0D>#kVfn~7Kdp#NSrdLgxdbN^+C=h#OY>5gXM%53h0y5uWH?Lx+_Qg2Y zCzGMoqke@$t{Q&2=c6$t696*(v;P6MM0&`!x;gufV^?L?%&1{K2wPUo7+Xfz*TMjjh58^qDej&4 zTobucC;+J~j$L6ng1m7Gxze`3?k-o9cp7;HV9u??YKfM;X3i%Ay{~k=Alby~=4Rjl zu`y#LSvQ`kIXF31K_whs z_TG({lFGy+icbC#pO-i^N z_TOG-1?&{7L#cM0yxuSk>$~jJ<6RE07ZTnCMad0CcJ|jdyG>diG2WX8`_H;fwug^h zJR#a`UXW+wxa#RPa;9f^nX!H6(boFL{vKut`@VTHpOfBs^Huj3E(RQZ<*Bx5Prd(FI%tzrXBl!>MrV{^ANM`*Qg>yw-RD5RQ`i~L zTwcamq_@0NYICP~`_@($Zf&>J8XhX>8)?|we%`(z4EFH!dQ@)HQ*Yn$Lj8RHbZO;j zYSKPKTNK4@o~D%vT*)W!n>eHosuTFNPvGuw>6_i77ptkYn6gNNt9gSU6pPgb52|X5 zFohV+SOet-<1F6PzmI9|Z@r}l%c(W{41Ft*jD3ihYSzQ@O`Wrz3HI^5Qe#N?b(+IK zuHJu^TJ!JF*un#u2<4mVv}J^{djCbKu@|vtpSQ86g9&!w!8A1&Qx@sn*Ev-_jErox zhALOS4P=Q`_Ha34W&XWLdj+ywO@#8WI-K_&_$pXObN4roAMO0@czZkbvzQzjFOaE* zdKQ(YCHQr<@l|gd4^EHz*vEBZtIxbNtvw1TjKHIuBt<^l;Z@PC1yc__y$=t z<|a8ZCQ*(w%dp9!d2wV(jEV|ix|Y%HhYK2=>F{LGd9?F<%N5R$K`|^g6GYmW0N8kK z-r8h8XM;Z2+Pp2~(coHw!UBlzZ9P9aSl^l3LJ*LfTL%aG2WySzqqlf-YSf71Ky{ZE zs-W#g^*HNU?0rphZk{(!R10b+!dH)uAnA|N-I%cNQDBPA#G!BRuW?ry!fRJ*kP#!^i4`5t3U|$7m zk;4r2uw86g)W6us!rXUl6C6|{vvN;ly&tq-Ww+B1SDauklfSOK%~CEd6w*!)N9APu zB~ELl@?W9^O5&UYKfVl2))X?e;u)m(!O3$7(hC&cdXJ^hzrtF(x%7r4%v*up99*7{ zKg@+}NFN-**X>4p4FmTSSg5R>5G(@Kj-ThEYA6V-W^Ta>27R#_)c)pl?yyIV>Bg}l z{h>&a3_C?_*H+6$rtw-zt=5~eDw=P~Dr>)%^L#ousc6wDYX&^ls)r{n^pK}5K|xeB z4uhC7A8*{7mH!Nm)lf-SmPdcx+?+=-vC+q1rDGg=ckbP@ zbWEBgw{8&Oa02v8Q!{f02ZN?lg-tI?+BuUprixUwt~cwcke8+~{O&&q)xlL-yxu1|%rCN0mKUW~ER zxLGtCPug^@WMM(@(954m6_nW>`Is4NLM8B-P%SpeN)w%;c5#MkHSGdHP=s#xqx2N-}_DT2auMPV2`VzA>o#OOu+B=15Oge|#=10(HqUYE;3XoUMT1WrCcQqtef@TBu(>tJ_%??0`bOWGqZuBTw%kY}*c z0~WWHwq~_<@ci&_dtHK|X4i(4%b&YSjxVpx@a~UH_$oU^iE!m7ai#%posV&3@elag zXVVOlp331nbirVFQecN(MC&@PQ(un55owi#kh40{mU=gRJpi@CriqoCwL86@yumEc z0to(gjsyA5p(W9B*x6a%+iX`-4`8IMNqxTe!}@<#(+{7n@9gaVaQp)t^AEzgH>K#q z;6=_!i&w2eX>J6or+kW1%Ri%FK6z?ZQCL*3P;D@=Ls{SX4!M*!z#S&t!`=PAZEfuD z>>qR>ts7xJK8J^nWk23oKghCqus?+T7pt@Z`+V**Zwd&9VGU<6T+dgur~9>S*lvTz zMa+dTa6@iE9+-D##3tn?l^cs&v>>N*I4YJ<7Z?|!t~BP$H0|SSRO_;?C-7UNsT>{; zk+`IUg*yG*MlDiceHP0RdKnF0Abw;~T{P8CmR2DXZ9>LR)jY1NB^645d$s=~NTopJjh)`9Xb+$Pu74 z6GGP@aGg?0!4me2?^f*!b@=Y$J6b5}gdDFohmN50^K$E8a@3o?ZKa>JT__X==FVWm zi&EA9eLa9JC7HrXIo-$GkN3@yfkfx55=EDJiY@yb zv43Kynbf(8la0zWxmrlTk{*M}1@ z%Qsh?*_+Cdr`zE9P7w`$U8}Y|8YZ(I8a<8${@dKT;s!~HSdNS?HC&%bf3^6*8< z(?vhGM3_=%IOs0nY%;^a;0*-wv&SpT>Kj(M8#I+o1niKcB~0ZFDB>>a3HkIaJeVJe zTbMmFZO$N^odE=CPQXWD77aEvL)Mvtc4roia9roC%|n#gHPxtf#ON(Y(1zL@=kP3@ z_Q9=3<~yGjYc5=6l}n7MtaasMa?#DN)~7}#C^z%AdCOjO+tR-aIFzj&)8Ss5f+@HA zuc^SaxUlN`P+LB32$F2H-Y5_syT@blbQNJri+1Oa%;{Bz6qCle?gYwi4|KK8wYYqG zl&N))%^AI{_*uB%U_IQBVy}i5>S3k)$Mn#E{Q5nFQH6Sr&7x%cwtkcO)lOoeLhKof zcVk7eRX@Jb;fGc#_9UF?46nfUpy<)bG2?ie6ZaLZr4qzOAwtF<#XhA>P;W4*)#fZP zM*qhDxSG9hs?;V1cCd)^-D^OMXEtkFxs!jxYBgZb;P^C_8X|y(Q2;~O5h1EQ)HvSI zmKx8c=5qocM4Mz=aE6&0Y40iY&;A=HfcT1e?Zu!_*~Im|d84t~ay$p_>Pr;Yv@Pl%}A zxz`Mj09zMSMKBogA`(}1 z_jRgP;TwwGDYk6TI~y8H`=H>o3~N9_#b`PzzZAphBdq~Mk zFuhpf2q}39MAIWd+&)k6EhM9OJ$=i??L8!O|c<{f(zN- z;gWT2M!f97UD-x&)>cwl(%M+s4cLddnqjKd#UZX|8?oxjElc~>X%gCq8wD{?ueYaM zY}+F!#BMyblIe^|#o#=ygDQ?rq14#-i#EKdMtm`QoNMiV)kZT{!@T4T?mh~P9Gz~nQ&KE)He<3Ff+4Q!jBpV!BY~sXgA17a;f+PZ(tUY7ELuYtm z#$IDzMleF$TxJIW78W?hRQiOj_y(0$$Yodj&Si>gtdGUz*gkjgB8$qU6|uKiCJjN> zmMXJR=5lQm8DMr2kgl(B4R2&Qg(V6R)W)bN1zVaotdANgIHl(Wr^BBI0OmagN=3Tw(lRc)cEZTr>9%Vn}=Cg^6Vxdh5RL2Y0tJOGHxGTvDe zHDbL-i)%>x8`LptW0`Jn3ch=-6|>Zp#5Es*(pY{nK?X9-iE~U9`|gWMm;O~ z{or5+M>|xM-nO%-!NQT}NpSKqE|KhA(XquQ0Q@gNYyBi{NFOc__uh`(2wDOYx++Ju z%_twXcuoe>ewUd9v560v<7xW?vk6UpS3+M~ZOAMLm;~49UExgSHEs=0JKLi-+z4o& zK!o~yfVw{wK5e|-hP2SNZfGT1Cn?b6!_V<2vn`?cy@s;roHdhK;r=j(pdw$Hht5n^ z#VB@U0m3cY$+h3+NOEio8)~$v!n2`&<}TBRI*Do@k;R7TQNEw8TYm8GkT-}|X)eJN zgbzz_g8t3{}SO zM{*US2LfZGVCpjmv;9*i?i@NUsxOssB&~&n!cTXKAre+*_0=fBfnX;ND)m+Gt$v-L#Xj8dZ4}5T+9k}Ysx}dFffwb?>cHWtuo}91 zAY|=Hu}Ch(L@UOXhiWYrjl>be-LmC$cBoK zX*uYYQv*hLxyEA4hT#a$v1=Of0Wun2|JE^I9q=L}qyw9lDFB%l&Jh!kfzAoUSJo?KOj1P=5jKzZ^3+=n_ zh6o3b10-Q)VtM(nA{iqQbbDbiW_!st0*!`a-(#-1bChCq4Nd2>z^WWeWc z`zLHJ$mBx`9MG5lk$-))@~T@`D~g2VFWKzc`!h9KRJqscBUg)|Vp(5Z+v4h!ozRXAJ$Q88sk-ZoFA%+Qh$D8y9gS*S zu2~fCOydr4tdNyzaGRWwhp=ZZ#b$LaVi`MavTW+Xf?7!pE!ghthDmPgabPP)y>1`n zY()c?36|ul9PL^`^-mk`taW%>8*db=>}2fsFt$dLGZaXRe;QtA8(wFdQf(QrO^1{Jeb(2uWPe z@hmA9W2tU9VTvAq(APS2exTT?+n3c$byS8~Z67PETJ-T>J zs5@iBW?`fegf*g4!kX3-6X^;b6=}Pragd}0DKSp zbX(VekKpf8nW~z3I6A{+bGgj8ql{ITI`H~KJnzq89i&>3@6j81ymIq zAG<3ORzkct@*Em>P+9;!cgzR_rcQQEisewoVlyRP9|C)2Y{BB%pbBM+mI+cpu%O~8 zuyBL7=~((9c+us8(pj|gqJ&)D3T?=RE%YBR3|fd~V}4lT0cv(mUyZD--bU04;G=*7 zEFyH{32|j0;;w??+;oKN56{LTPwgSv*4^kjMyjatj?d&}*m zZRLe=}8$T+wNDOye*O%4&&?9m7Qt8tlj@5N!8(jyIrINg=Hp)cZ? zy6fGx?hM~(hQ6q22fm})_zE-B9kMlcvtt|h zKYFlRwTk2L*FxiWL4aXqy55bY9JKW*770JTDwYAMkX(|Grbf&j8o0#T5omu(WGuLO z=vF(xEKDZiM66oCctIhr!bSNOunHYST?1HD0&oyZi8 zG#?rL`IQJ4J zze(59?5EY0cTW<*ExJ9UQ*nD2$(O{E@W^PCO=w3hxr-KL7mE>-pdg-0d`MKP_M!$c zxVoI16hNCVR-n`yvD1{5o)MYhl&F!@-mKo?4QalWO1Z}0cu@i01&qq(&aG9C(o-jQ zO$@J1`KNz#Fbc`EJiS)?Z_ z9WYZ^=%;SC67osGo2>ov0J%U|s}SakJFz5nQ-oAc)O$xm2mMjrw z#y(%aT?gI>2dyGSAKhZBwxJ`!MUgs7;WgN>ri#~A7aP&Jn?!^@&(5B+E=h>qQDc9h z+ied0T-KM@Ph2qc(@ajXoQuJ3Fe9&mSQ_RSm$#PV=R7&~%?)s6o>g*^qBDXB8PDCz z=h6-?+7>Cr4k(HjWRJ^LVrS??Z1O*CzJj_L>;SoOQ9Neq_73Z~9RFwH(GHNyM#bWT zVA@;p?EU`1kp!5g0c^;mSgDTR&(hS{s%yQ0)>D<@P*LUx(m$=`a zHrtX%1-l?SLgA8qWEMz`-QKL?W+K=e_Iy#)o- z9Fb)d9b44&5#ZzEI*dvZ$c^fK5UN#50}RiY7VX^f>bGH@Ht6sO8u;R*aRC$Tgck_| z*=DZ3u^|l6oTX{cJI`DcwG0Q2;Ea2jSNh{FPvJ_l#s8=4w7RI^M$74gVfVAG(}h54 zy@H968|85#T6gOX>i=BUw$FXsvc?dHUD029`}0*gY-l`$qVPcH$A80D=}KZ(bRJ!x{AbYQdD+V z*#d}z3-K(gFiIvDpZ|JP=eiGG#b^vP$~o3>8$5{Ma+i7tbj+vQ=6R3E_MquYBvk zXtu=J!^MKiz=vq%yzu;g;?I{Gd>d4Ogn?AWh=%}pul5+X)Cko62rBhT@N-T zVirAQkAN8E7W7r~wo7$&sxotN!O<%y%z?bkA&i{|(=~UghOt?wEO_p_Hc-V(6atZ% zonJ0*Gnbn@L?Jvh-dvxbe-L5|HRhB~PS+n^;c*U1NNQ@SK>eW{vxg^BlZBFSgG)cS z>#}_3-5Fla@Yc;*=0(3tfVG@e;X7->uEG$2Qls~Qs$draTv?^?6QN~^L6S;1;!2vj zXSC+J_N@pTJ8)2g!R>;w#`mEa;bnOR3vG56I~0YuLo@;i+e1M;el{0ci17|}-(HfP z)yqv_WLHIS2_q5Ujg9)ojb4RfMf;&ryJ?hOhGM);RE;QElSa$6I{SG8I2HsKLn@G! z9W;i)^;nPmyjhJGkiyK#6S=y6OO~$8V*+4X$$(gDjVt00vC4<_k+G8#iZ? zX&%KB3xge3M$y#(j3SPHPzG4JP7A<#G%-4y)A6fzqfXDucu~mz=m`7FI_eDeM@9Ox z5EeFy*T|&JF#5I@Bzt33(s7KMcEr!@?f4WQ9#4*ii@jDuO1RutH7xbDG`)s}TF_8? zR97XK+YP2Jqfs7jWdx*SFqzZiv58^p(asIL%m+#*by9!P;eAW#nfr=)A0Hj;?>{>v z#nk6-?LFDv+uAxfj605j&Nm$ITQp{*VfhMPqUb~66y?i^n=y-U#&6VxX(S^5JquB8 zZuJEsrkQk)w$~QKw2G*0!bL?kb!`R(@0KhmH_~va@PbrK97qMxXq1q0Ie5lO3gnlb zL@Jk0IRyn4vZsi=sJ?P$O%^pt5;WwdD96vMEhYQ{3kY4P8^k#qPSFHa7ho+K18}<* z&wfL!I-#AX+*e@FWbY{FX$=Ai|8Dcml2Pw`gFOTV=TPPyhTFlfDn8+TZN-JFXJR@c zX32=~&HRbETBO3&*LHz}DApfx$Y7*JY4w`w1(}|)4&!Fk z(#pM&-P^iEa2WVLcJrfoIcPJrv2ix&zvVT(!?VHQ3It=NKg$7q2C5{;+5oKKxVH7h z$=$XeL56RfzlCAL`7rb~Q2`TebWBK9BAwdRz0ZwsJQ!bEYWP;Rw1nXaQXZGrA87|| zQAQC(6!s+$mk701%`_`*Hhy)pgh`n;7%3K$1;22)Fl##wc+nFfFYyWPBAmJ$lzTm3ayJg}Jmg_Rlk58!wX8mDHCI8MuRIfzmR^$R zmFA}l&f3IOih87KN^dB{GJoc`)9Y?QPk)dB8B8rx4FN*@+^>_DFBYPV%s@vjvb?I*#Bc3nAuUYx0(9 zHEx&)%WZPW6;~)=JB=5ZUMXr_p}?WJRC|eNHxPwu>9*)8E9J<`C<`I%+|~!?P}mAW zX{b`6r2q9*;`*Z}YP2)PGr}ce@}AT-yA*5_n%!cNEAMqEV#;~3Oz1eZS@Vz^jbVMG za>o5Ne1xjsFWc5IQ+|k(`+JEEE;4AkxjdZiNB-){X#7>^%O6BSh_m#E)UX*YNY8qV z(h+tDB4I?J+~7O_9`B_QkJ%W!=UN;| z4Ee9d;rRSy2=_i`iHCf5gd0|6rI6+wPX~e^I zBy6+U;D4;-Y7Fu>k`H0^SAXyjVC4oGS=!A9A=zWBWU4Z5SB*XhR25=xG^s%Pd%if- zl=&lAe_aJ^KAMb2eb|~@(3f#M#guV`if0c|%(D2|Zk7p057U;ev*|`z+n#Q2KecEQ z?QjnxNIS7kI^n(Ys~X+o&Gn=8N9%`M#8|R!s2#6!1Tc2ID`9Bb{f_{lLy>jrVjm71 z`f%Nc;Fqw=Zn`_3t(If@748t<{erO%(!+H4%0o}$^RIcsiCbUtf_}FE>#Lzbb8IfL zh4KuH>$l{K{(Mn@0q#MdEdVC!G3*c-|-LuT>)y}`Df98 z>zM}xnYIBT?BAOCm{X@lJXiFYBfYqi`B2nr5qaULMCgJB6s6u3uagyGL&4bx;s?|@ zJ1L{=LD!()FP5%h^BF_wTD+uxwO6|GC{Ly7D}|YnKpGSO|4fpXmW6(V7;prY-8}TN{No|3gF6v${|HV8QHX6Fdtju!q%n+Ta`+5(~rFayd>@IKnLn-{d1kls@Urw!qAe5&3c&h~iH zUD09vlsm4eQl1`h+MqoW7X%ruMsLZi+({tgiSrA*Axyuh-DihZ2<;>?W`*vL`+Hl* zPY(8&BV)IE^$zikntJetgLv0t;-@yhGz`P;VsSw1VmJ$Nd@2{i5qWL138)PgNe3e$4O?QAXTJe1E!d136j0mv$nAM?x5qYo=(OnH^$^vHcV{*Y5Ux! zc5RRE`0Yp&0W301-e->tL;Cz-cy+dZKI~0f33m_2q5v?PgY(`8#BIR>)VH6puD_m+ z&+$@HLM{FZ;azyKS5i=hbdro*7&-Q^ubYF*^YMpr)K^W-yuP$)o-(4a#S9cPZP-W# z+XIVrD}D*d?LgG77Q;&^&SK(cB!Sb(r5N8r__5E%7>)QMxb4y7;o$tlrYEMpG86+| z+j~#MM%Z54+YMuPgQ438Ek61(rKUX`!QC3{5Z7biXl-0)_InitlY7ukoQnnUMqmrW zc4Lr?LiP6g@Qj;6kC;-!o514+<3B zPRJ^ecAQv;x+YkU<=b;YUAAT8WDUsB(6u7u6|%9&=gVvm8sqqGvYA=F_Y*&d%wu`4 zrnQx?Gyg5*cWQ;>7*Jk_o_*4~i0$ZOsqr#I5`57Fye;%s)&2j`6;4C4^ zK+qLx7X6H~z8R1ubMVm5o46UY57*5Go@0~Zj9DzGVOwEn*q{e?SQEm&_9b{0V+I># zd@e=Q;s<-Xxt>@vJ2yYuthu4|Su-nze#IA?E2gqQpw`7qAT*ihPhHeyL0fd6Ym@m^ zRLmKkobJd&`M!RC39`*0S=+~ZSQzzEP1Fj?)}b9swO2fpiZS*&u`uSun;A*NNNSx; z#iz;+^`{QPAZjPJC~VT_dj@UM2CndK^jCCp zipPRtf;)fiN(`%W(}uU$uA~R|s*5407T5#pO~6?_+n=96w&+JMB%TLig4ipD#7*e94L)pQ>0T* zyYXyzK3*gYI#;&uRyoqKgQKxP4`t@&Zigo?`3!K_l}Xl24m%cg^zG4)NReT)&EppH zoMGA#WbDq){`#i2{sh!u8WXE@8CG@*3D!lNM)l;fL@5S=-+Q;~s-eB;fuWZD^*m9Fcvv=L7c} zeV3gyquj*+GzQ`I0PrELfRy4nt<~M09_YQtRwdn>nOeE`juZ&1m(+b5i|`6)H)iWc zG&MR|1mKWmle89&5=37iRc3g9UO9}T=V@L%OH=6SSMRUJZ@bOCeZ50xAj25j6nKgM+&YBc z6h_$STrLGA`xZ&iol`>V<_xL^-BA=T?>y2LL_0qF(dI@qIB5&RwpuF;G+VNH^VO$< z!qj(|d{*Q<4Ikac9ySrX)&4)=KusTORM$>i%f_s)IixSY{4#_e3tQ=HM0{ZoE`;|b z|Few-+Y1~XqHvaj_(WgfY@{9Pkz&);ZNt$HTa9Qp+v)eHRT$i`nrtk52hl*^fjy}L)&xgNY8NV zI!FVh*Wl|#^>PI)S0f$%tGHLS)9!_M?H><*tH`u_xOJ4BQ{Kh93FkNu+Zf~chmfYN z=Iit8K{I4fmYueK(edWN`sViYLkmF4-JN;OcEBNR(MASDIs^jo_#OWYQ1=lE4Kj=` zQDK&rXaV026nJhWUNi_5sdRKi>V&*$$GU1}SopzCz~T_$>Z*U%I*tRi2iA=+na81) zRbuL06Y;!7MHEfViNZHz4z?-uRxAi=!U6A>;)-|;a4|YZBfOy(76!$&idDlP>mbzP z$(ymFOk;ue!ryX!$t~7j)i#wPJM=|sa z-ixyq0_bSon!Ig8Coa6CF9Xx|v%BAYakSc7;?sr76B=FHbRLbbpCu{+WUQ z8Io~$0QbfrG#pjDEMk{565;6{gzz~lzbmF!5KEMhN#uH1PzJHo)?(g@GutvT%>YNU zO6K*|wULz-k3t|DCPoLKwDEyWn#IaWwuB3&0E)^(?wnG%8o83`s*d#6skf?oD|uxm>H9trWG$g4r?) zHUk7C>ErawY?oY=dZmxS$!kuw1gP$4Yj^*}7I%*r+59`x{m7!_7c~v>k#&&5c5ETo z+M3xX-yvAuy24+zY-1JArLH~*y;5QElWwe=aR?g_*b>&a_eZci*!TV=O&;s#y~zbn zcLwX%SL0G9)L7h5oDTL!>sME>XteI(4X(WQ!T#3%Wp zEg{$j(j>S!lvA`d+M;QT%-ef-rxqPsfhf1JU?x3Daa`hY;zhe+AT>zPOtv_CZP`5XAgy=k7Qe9%`HWW$y7>(bl zL^uaD3e$oKS!vC9BRD&rB8Xp}uZZJd+o~vkJ@&wew&2_72(}Fdtynx!(!V%B%0ZS+ zv)}|P%0AMGdPkDCRlhzVLGhW@(EY}tiTaf{BA7F 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

KW-vf~bzmweI6H1;6vbms8^+Y>1K+ySb z4TFYfN@unRZ5g6Y+HtNAA@58ZH1#ao&zx;+u*{(Eya|p$I=fp-w z5?WCH@GwFFlFgyKc`ij|TrgZxkdJBPCtQ#ACtw4F4L>?>ZfKxGuM;I9m{jU`!)%-@ zmHBCTL&(-2?#N0HTJ{1`K`Dp_(FQSPMVjnPVMipxi5%yE)AXCWB_XhIIOvM;6?;_* zt*Ax9YLHFN*f{K}P)q~8hZ*9kWZ1bz5cHG!s$J}s4;-+PiseBU=Ny-Sl@s#el%nJk z1V#Xkk}Zgtp2#(lE?#oNa%io~yH2I7H=#C zAjQr>s>Dh-wvQMEAjJYt#XT!9w3eRYnhJ+ab$C%9h5Zpn@iLnjlh3CKH55{OL$6wlgjP&E*^Wg*79b;7Ne_q)`o8L zac)d2=LJQXWe$XYsNHKT+<0C+W-sA~0o$PP2h%p}T*xbzQnH)^9*lNhORvX3BW*P2}fq$UM}6<;1!lMW&;+c}$Na*a0ofc}u$s1^ZgNou*)HX9~j$b2`)qw{1ceiGlDuXuhD zd&M@L^_a`c0;UD$6uo!N3TES{4aB?IAiooxqpMtd%z~r(DKo45M(?4U$~5H^H$rVf zFAPXh^cNy+g=Q01lL1{)@e$v!E%oRLbcj>3weHX z2H^tk^8NiJ6bn!Y>4k<33oo$~rE*Xwxz#A(dl{69=^wqbTA;UQ?f@D_ zG!V=s&6V@ssRtG@0LC;>B+FPt`nI6jmlbtZ7tORj3Wy=`D2NfVhm~NYwO?G06IM_q z>WExVZq1k@>CyO`yp`X~&IiMc<~i6B;}td?)&3njDU^L5zWkc*937qP?swb$4qM)y zwD5luWlSEix-ZOibdj^AJ26bopc?xVFyK{oHiDPPj*}a_`g^rE$k=R!WZRn!_#JNW zt=oKsXB`Dic5y4lcp#!9u42QTofii3G=xJG88qOz4nwx{Oy_LEiWd9D;97p2B!_ik z7u9XYb9dXH+e>a~_(SF>BM}SOllJcZs~*#Rqn}+}`xp`oiI9=~f5dB>lrG{hARUyC z7X247BAtvG-=o*_79SN0`UM`fVkO8%P{IMSI{LBz5k)6f#19o>0=OMaFW^_FVjSVu z)54IpZ_Z(>xM=(l{xcZ3|BJPd+b_hnbYsYwGzT$6f#|A0fXFCO&Vhs+AR6a8S3f@3 zk&$LqkaRpyj@qy)X}Te3Z?L6cH!{l7e z;2bM3x&v-I5A0vcw!z+-=Qht3Z!ck^5Z(f6HfiUeNYgEFy2L%5$v)2t!aS(Y-dOYB z;@ulJQ)7@79J8O7^~YL(nIttt%HFs!p%9cWZ>JEWUjzWF=>-V|sYLIC&@Er?Jxy5o zMIyq2cSwrtKrC%pFyx;m29Rc%Sckf@EG^kA{4|l0~vmH4e@eW0ZQT~c63e?U^L6b!iB>< z*0gNE6M1VvWR;E>+hTb!6;bD?8LXTPWbR7}022cOR1QYq#zcEc9_TpzyD;E@B9b1q zPrmP*@bVsjZxI`9;<$Hkb_9p8=H@ou>alYdnJ_WzK>b-CGQxW5#*9{H)}!K{@tD$H zcJp5;VJ5u~y0|TKp-*KPb)GT7T2E!mXA!6k4+zI-2nl8~T^%Dhkh0-J6Q*#0(C+s4 zdoTBo!!b3V3LGuqS%-sz_Va^I(wUr0Z{`_=9cxeKqu%A{x{0M4Oc+L1=3ZqJ*f*qT z-cn%dR!0jVQuxx(TMl8SH7;;CArr-F(1GpPJ4Th^{$rco1f(-3VaAn+Q-$@gUV*~% z_P!s^?Fa4M^P|J(bfIxo|A>mur&n+2JcHF!T0z|`RP8pPayx%P<|h%Da_sSAHn^Tn zIv?n^MVS7dHv>LOLzw8))Sw*!+6T+EcCi_F0~8C)qQe#N$SP`sBqy)B zKeqp|4pmNWCQz0gZK6|7B9tDl>dq*n2qVAHrTfrHX#Ws&z=J2p2u@pvJ9Y-MYn%ss z-aHbJS@1o;NX#$qORAmrTB5BJybNk@x!9yq#-U?bVw=U8WjSIO)5SGnH)5!~bvzqQ zXP8!nz9|LDWao-`vD02l4E}p|GJH2^iMS_m*vW^u(p`JTb7>6cGuhsgHC&{?;oyUv z7>3eL)+rv#!Vs=4(d5PU(ymcQU^@zyU`ip`Jvpo0LC3q(*_+Y)?wz5gSFXo_?_MRjw^atMhTqPDvKmA#W;uc>uiV@eu>7(ahp^wvR$c8!;e*w6C(de=wT_R zXv@ka4mgqx3lv+@%k8pKCcn;x()-q_@r#w374&aLPoJ z%Dua5(;oI)4b)66Ux!h#PdDypB?5c)jgiPGPvlTjy|mV_89ovVHw_ z>j|Y&eAHS}d=GY^_MA=OPM|V@ z0@~6;kcI5iYNIwTC|QS|w@*&mFG|<$)e4hVE}uSYZC=6=*NG&aAfj@4F4D}%Q=4+t zJ6p2{lZ&zXPcvlt)U2Fmf#ZK3PtAJImQif=r;+cq=gvX!RIRXGhdgxr#7oqEIc~_#_>vUhXyF0iL8fZ;6>;Jor zo*1~={eFAr`@7L2gistmSzm*lm|t=9;+B*UFQ;_l$L#DP+xYEyxP5qd)O&S&d~ipj zgs)#tfl9&p2^f6uRp;P7eev~w|Gu1b+9!t(jX zwU>9MwO0NO(3I8lq`lL@hU~6Fs{o{kwn!!Hi-vD)VHK?=N>Vw+X>6hm%81D}fWw;i zR!~9`LKvJP+Hk@^B|(dXwqOAhro@F}q{~$VZUOst1S-Q-@BlHF_9=9ymt;b@+d{)t z%SY-G?=Q~cPDe>f9Zu^bKAQB_ea6HKhVrfy{sgo00B_~A``%Z%f`%epXhS1?gvZrw zdrr%4y-m&m+%h@0UtPZt?;r60IB(dEmTWx9I7wB-pIFGhWrdB zNA6bNS4>j287!M|d}cG9$yK3}nL_6@OS5~+vh|>634k{Hfh{TEZ10sQmf7BU;`6z= z6L)@lq0-LC;);pb6q&&ek7|W*Q&?>8grj|$Tir}VeF2$dX4B+yl$N%dTwjhz4nNua zdXXTHg4RByv#&Uw>2R7OvgrIWfu*O3+HgM&irreVd^9N*qg{De_Cq<&AHp(vd#Tv3 zTQQP>U@}SF*rpyA_EMEdHZ*9seH>7LQd+*OMf*-1pb+p0?liHg#HW1Gc;JddxC)zr z%Ln#nfXYfGHd_r*I$Qsp%v(=9HC@La5ac!4}o@vQ7eMddr(D6w= zrq|cF2ougvJM_P$;Dv#M@o>=C&DdxcHzd7|)1 z;)wie*xZ!{(1{nVQ404`htxbPa(dUa(AtYP>I@KxbnE@4lBYh@bEP#sr>-c zVB?M34HRrq*va4OmFkHk2Z7TiA=UDHdx&K(2cSt)344`@@&sFMmXJ+pwO=5OZ_!Oj zno?LYeC3w;F!eqjqw3HTHq~Cwi&Rmjj+fDb-&muQp5-LNTYdfgQm3W`CJ~y^Yjn&h zl_b-CNY})q{=k&-cKX`0s4ydVW-V|wRPjT-sSX|ix7rtI99~(szMz4Y@WyA!&nwrk3sR|qltcDVLN8JmMb1klhQiDbOiIglm_W}l#;uDs8 zK?{9vs{l!v)l~NmuvL)SC{hc~ayr!n#JPNDubj4Of z47~lSpmC9Id%xGCbGp?ay`x+eL@H4OPT&+*1!+BD8TyJ`GjVn4jo#x4{p3v^+@|^W~)vc({JA>=Nn-Sv6-iAHT;lHoxBiyjTolLoNTF}RGT*2I6 zQ<_8)2=K#2$?z-g`L*AJgzflHNkeP>HpNffzXb4n{UDKdFrXN)O;hh^#Wb`|I&~2} z`Bx=9J?(h+Rtcfzh|s9ee~Y;3SjqSW8^r(t)T=pw?a7icG}U>Q=q~3Qc9=zM(1Zu&4qU)$kIETvVIBnxe z@-+F&Q>0K(j}81C+6`xfHpR^sI&@3&(OH%}KX}y%vgaS>>BgHe-r{OKf*a{S=|Tw| z#XO?0U8v*}@Q(D4eBYmP>AuEIHW}WgPyf2rdPEm!V7MVv$SZ4_T)Bj#eb{=0x^D)# zLTJsIM4Fm!zQR%$>F@1Kpc z_CD9wua`Ghg9#^4&Fl=xTEKn#B1gDHOUV%iJ^}Gy5p6PSA2B@rf{9<+n@)fVJTeJ+ z4(x`nAufC&#t-374%L*ewaFuK$`7asFkCAvB=yPhWjGl7LOc|7&UQ4>@lOAY(V_vO zRHR*9Q&V!ojMJi$LAomodXz&vIqEQLGWe6DK7Sl$lbhar20F0JqfzKCZ3NAt^?<`i=SRlR#jk`Rp zu7f*;i32w|Cl};w#A9&il8z<@d2c+rxSX>?xzu9>v|lpyIAvd^p5mie4$f{3&dy*y zyo6ASC}N3wF9f!76o-Yv>$T~#CmF_N?UHixfq!6IP z@hF?%4YtXRwt~qsm_5f+yy+G|8qrW-emuB=l-c?ly4#)dNcIO8xeZM3=K31aJMRr= zSvEPknP1<`Ew4M};TmTo#d7WFi|G{Jr~Q8KcyLB>h)J$N(w@Ul}wD#)Gemjx&XC{XMmOM-Mq<%v(Xy@L*mTRpJP>^I_cIkP}5S;_KZ`XP^T?* z)goi%9Je!X?;<82M<~Em66xlzvZ5xolJMgVSYKi8PB)IQq>jP*pup>=_~uoIKAz#@ zM}$L^tUu#xZ#uj|7@9|~zrn{YNDr2r;oIQLazb=RpB4Iq)>#JJe?K*_#IBq!P;Uqb z1Kq=APWt^9G|(G;pr3!GpV>PeqSt@JM~B1-<@^Uex+7^|>j^&kT*$E5-`h(ceSFkA zZtr$_fN?!I%Lw~!=N0yD2dJCf%x8l!lJ-vaJKf!bf1ryqOp3GdEfPE3&LL7>PH_!w zOjtcXdeK3`^XWxK`PR3=bcv_SJ zQ1`$5UpIg~=UQ8Y$M>T#L);=f4n|i}lJGbjy=5e}2#YS9d#=Ez1aylqIm(9v`6677 zq1tm6VSdOVW?c3up@Uml7o7Q&kbyZIcH%3l`jqhb0Sn-i zTX{+t{dhToYH`cigw)?>1Nk6?{z3P<7~!Xc(LZm-Hz;WIr%){U=$Fjs55^(q_W6`1 zwQ=5KDn;MK)i|JgMUYKxf+cVU*tt z#)z+l1Y)oa{DYs*e>eeSWVcf|!)Os)eSoj6N0Zk7kB^@J2}km_ zN=L^XO(&hqQjKc;mvGtSlwn49^o`Xo3n0tLTwFs~pV%bgWz6^gXGFW_jLZX^xaIUq9Oqkncz z0P>e?Mw0Z0b{C^U3{{l<|6mwED z@jy9oaD&bwkv4?xjmwM#luUHTq^@poi-x}ki@-$*d&45e|hu@mPQ~t zzntFWa2P{kL739chfdgzx~Pby9q&T%?uzOk@9*p!blP3QEdee`L)EtiEo2iy2}S6V z1SW{=FOY=$5?I`Lbzp=KUmf)K6-QnMj?m@K&i5U}CiRfJ*#+8tmnDsVOm7+kyk$(m zzwoT;5E~y@F{rTuG7gDd%s3B;mx;#i6vwSJbdVm$M?Bcl1@cvC*;5YKrXtYL`3OxQ zj30U^6}p`l9-d*=;x{0MW9<8IXo)vR)wpv(_Qr7H(Wa zgP&t-KyT=RL1+AE88-WPL?HmK!_sT-b@mSSU%bR_&b2^vXuO0dLTi1oG)d<0aH6!)>qKLw6v6 zfZaK+-BY@z?dhki7Z_jOX(0cwbMk_O&tZo5aA-#Nc4!gdRgx9bVRkK#w;+im2ulZo zECu;U1M4Sk?zw1Kq|<$YD|r1j_H^rIe2=YE@Atsd$QltiA|CTN-x=c(2iym|I(sB{cQgjjWAwkwYnujRav3!Iz><8 zaYHoppp9n$j6dNeyn{6yp0=KYsw?4&Cd~ry(p$(h#!z9%fH)XGnQ25?{%N8M&=+UO zp^I-0#!nO8R06%9CeWI+-wL(g?tL$_p4gc?u@)%wQI=7K2L^?>)BYi|-}yTp9^@rs zE8RGNnvS_G+PlubxX@E*Z@TC$FZc|y63W+^FGY(VOi3}kKWn3!*?2#iU!nx|dZsv# z*$c6GunoLsOX0^yIB$D-1XWRXN+=CUs7WW;W0xwbW>Lv>hk(JX0f^p@oepMw?>+1Z z1hM}dnqZ&woe&SA8#ekMAMNjw*e5F=O1$clX8EE^6ZzFd%o{wJF~?qYdVNZFb|94Z zrDZ?PWjlZG?7*juxUlmf8{W{%=_squsROf{A@oLCbLCNHA}>?}k{P>L2(XoeFvAD@ zo^B?*b8WBzl;c{`ZnCA8SpRw$rI3A9a>;tfHr19IbX&?LWV~tF4*j;}_tR#+CZDmC zULn}tpIl}LO2%%Ct-anMH!k5nUF=L&)cHioC2gj0Y-Su8xIARBotZt&*saf5rps*9 z;+*S>ke%9rvNJ}P0N999DyKNhK|_&W7xF|m#x)REjbmjC;+@xN+H+RsckQ}Mt22+f zJ!ZuxIfbsd?w7QuVRNy#A{5BtBa6f-;-l)C#OTr~54SN`|P>#Y6Mx-u~W^aFD}hPL;D>@`v&G{&( zdl#9Gdthf7bWxa_w!+3c%(#{a{*iM)y1t=_i92s$Mz=V`Fw9xQzC>$-+R?W=qV^RS zx5{1F#;EhiHXb+jCosz5Q6QR_I77t)N7yde5s)8^$XHqGSo*B#hB3K-H`-t{!9#<5 zp=)uJ89%j$hPaYDJM)DO!mB)cegiWl&KLNEkIU}J#)mMb(*?*-n0;l80x6R^vImI@ zHb`MM3%&)8lTfMqMb5)GXp(#{w^IL;O3W6Ip0vgd{3ac+^dtAged~*^hg8S|)KWe{ zi-A@7C=0h+SW@nRA?$H2FNH%s7JUI$Xg)!8(piqcWdPg>n4=rd0G8sxw2W=?(l@hW zmL8YIMd-*(Qo2*vb*Ey0mK1cyoq_0hy0aH6fobbb8;VmpI@Ty=E*xG7#FEpAC?hzJ zkaRP`+sTEbj-WG-b8P&udl7rIq~|x286y9jfmJnGJjQd5R`B#2V_W(pJ9Nq!QkNCG zFhRZ8?ICJ7qo)R7Ge z9Ly3rij&mf*lnRkkQdbnPPCYOI5GZ4panT1cil1CLcRj`M`=<5)-Wa*Q}HRH*odfD zI0%&V;DqnblEO!OJJ?OIK=9|jquFpaIG-mw)A77mQjUszQ!lW1;aqg%Y5-NvA+Uo< zjRRJ-bNO^O-udN)`yHIYX=5~3!*jSGnRlGw;4%oZmGZzQ_?5sI8rFct)AIUBX!(>v zF9*4jHv}t5^jHa9jKGnp6qaPe7(Vh$@hJ5qLzQ91XGB)u7`JKq)7}Z}4?EjG{d7E= zUd#qpFowc3_0v!Ax_{gop(PgW)5qYS$=P6x`J=5GjnA`?$^nUAJlB#1`X19c-#1U5 zJQX~Dn2vAg>=D6@7;joh^ys=MIw(5sl59Kkkt6M5Wt>rBP7mI~oP@1Vo<5W277yGc zz4XvcvgC9Sk(|P0fCG}gXf)d(9`@{QXzF;>jeR&Y$*E^~VGYLo-?TCq)C-v0 zv%I;1;R2;C2gFCvT3ttu7f)3MFDy^N$*QC+oJmS@^|_?vn{bUzI3>#jmp`wS^xbSY zhEY%z0Tm~7EEt^VOUcsHV#z8w6O|0<^x|QsOStlHP|KY=M_CYY3ezMUTdqj*@eeq@gzY1p6skP^OPIQIGvo7js90fg zAJ3DfkDJf_^5joEc?6vCuLlkVDUlB50HE-q%3DSvpNl zQsl{hAkGPW2;v;C(w8}_Nm=!jvOR*hGsIwGmxeembU~1u$PIzk5H>}Jo8pq^xM1gO z;?*?&4j1W1azV=N_jq$!Z-YObIVM&yfek#bGdv%fIFp!OU&D@~xwyy5Qj@(0eQt}+ zE)Zx^|Gv7eERRyi)pBIv+Q91z^tN(EjjyCEpNnjKwWN@Why4jE)F=lhoD~JdekbCx zR#Ci(!?JZn;S&|XD~jPjVrP7lLyxFY(5$aZdrk)6dLX%EEtpJouZ*ogl}p<^W&LbL z1(AP&99>bqDDPJkmmTWSMCIU$((WLpZaHVxT{x5R)agUL4LVb4KmlS zRe?G}lWwg_&v)Bvl|P2J(|m2zDA;9nGpEE*2UBR&_AId3Dmh$i!JD40i;{)dL;wqp zylE?Tixy~jqGrK41k%7{u!*+h0W#&OFm4H)-wF`1OyHP-{1QYRX8rD@C+4-o4sLb5 z5DT!FUAG&6aC6aU($tawQo_7%L7ol!uw^*R)Yz^L+a3>gwSuw}CIAIHS z7I-R(-5!sblFL5c6T`cj{H_>G7|^rzf&ro9jkrbz)?~ar)sl@Ysr?{>X0=jd#G_Jh zl8m*FK*8}pml{Y73!{H(KNyRrPc%e@`sd2va27|OkmPSAh(JRNZ8h}Hh0S4K<_7*UjeW@z&;7QKl(-!Z3-|3DmdO1o7vT{^cTxuwCY}j%zWF*>r&{XdAdD4RT^%$%~_-7YCid>K<{@BM}GZQtN7xn)=B2N zcZ*g98z)EXS^koxErD9A)(=EN5X?hC%dg&C>V*1J(QqpQP?kf zieBL^l!j$O&%Icn&75$rVdwLV!eyGC>jN!PUnI}Yq3dn5P6@jfwPkh5>UohP#xcyllLIEtNP^wj))Vsdy0C$9+Sf*6ULDwo?4a+fzQ*A z@5KK6DW{j0`RebAJua9K>k?fkVHe4zvn+4`lf{DO{+%bC$`D#7A7Hs&wGK;bC6f)_ zdb!C}za-k=+}?LtEL)a%y2`?+OXB~Cm&jf5lnsEvR4P|*i#7$w5>{17wG^esYpc;R z6+p&T^y9By;UX1Q%86#b1*arEx5Da#L;C7 zcT5MRS`A8}49GMl7H^zUxs*#|@E)`49(#-eNU6W6yGgwuc&LVV|4dv#q5>r1@mgnpm_Z#OgB5CR}f04wvl_ZDLyc`pi3=e_(aZCP+X%=mOKL_C7wy&8B7Q zPZ?*$TDhZ>SJcS~!+#|VKjnYJWJNF4zr`%w&{3}ZBqL)DFsa2Z{TKxpq$>t>j_ zdoyG0DD8nhBqqpZZFot}a7}HZ^(6U^Z!}B{8va{YT}Xx96;UkAP|~8Wj1_JEjF7=Z zup~YR6Q^fSE#VJTlz|AU#qLk#^+c{Sv?}PUjZ$DacJ}SuW-uBJ@+Mg)N*SS861>0? zNIiM&EbG2 zF{E+s*Qb|8b(|p6+4VN^0xOnfaaIaipx`rr96rKs1crF$f zZ>Y<`u~<&jWUU3gju_PRs;MTXr}~44p+6Ju4UJox28fH8r4exDu$O9v3!8F##u%IJEBC1-EhCSkV2ad*tpZu=z%X#q9o3*c8t+G~iD zf?R!Vj5A!=Z`=Sc-f#%=z{&A}BdFCQw%}@U1%XPO<7>E%HB#EmRJdNbHeGSKJ-3)% zPM=65unK3a$K+&t;-Z`pmPz8Sf|kmEayqlW`<CL=B zO?r2*Q!2)#N}J)CAe(+P$FaU?GRT@><4S+a#@2i_N_mZaGD>sE!J#z6xj(GUn7RZz*T%8$X72$32|rfyp}$p-66M_zb#0YIx_umoMoHYpR)8|R z+Vjzu=7$m*{yQ03V|X(GM~BTI>x$x`QfMzlM;=mMR^X{OH<1i$w-1S#d-!uGuo~xx zctie}($n{Y8A2T8bGl0=DSh07#Fb(k&=AFfwn98v;7Un_>rS#PPFqm(LemJ9KND0- zNYkgn9uAGIC#E_0GS3QSfT|w6_%eSkl-VjN^G&e~+R{Tuku=(sA3+ncJ{hO9sG}`I z(HnWFa=<^Ot8W3MXGj4`O%eIsT0n@X0q8WY+YoXm{N@DgLk+4+cLfl^Ch>5T%Y8%K z=?XrF!aV*ubrDgfHWV!e~bq%m%u^UE~{81(bX4PZs93-P4`*g78PBPu}{A5(EMn9`qk2p z5H(Pye~8dF-ym#m+QKEwFC}n`F+iRA46Q<5_Xy7J(J`+;YDH2WF#_;*xMrD1KfEb8 zIF=pdB01Gw#B z)Q+R18DZtrrzx;n*0L}xPX+o%IW{)p1V^h+#ONVMkQ`H0P83CbUtGd#OePEhOcD}7 zv3X2EO(vK)g8#LArkYHW1|k4Mp&8$*ub7n+UooE#XR<;GN+6_oM!I&bfmIWq)X?ep z40A#kfdn>2y;l|NT-h{9fy?|gJ`F`C?P`8_1rVVtia6=DOEU5~Z(uS~j1jx9iRhIG ziM#cU+c{8&%48L4I~*60=FJP!q6B9SsaA_TSjtCdM(}{t;c`0$Yz+`aa!CJw+EXWvBvA{X*m58 zlNbyEqcNQT+9%66w=*r8uXsMOU4ti83|;S;)sTr0?zET0KnL?Fp(KdF4N=MG8x_-i zQCPsHOe)0yOJ?gZh=JRRIl?N4lQb7!2 z(7lS@&_NzK=MToD2{h@S*6wWZzD&1*bcu_rxg!bUArJssL3n(41(OAt(zgjPH^aHk z64Cm&1&jY*{@i-#J8?l1`&{Us@{gB-VyeEFs;d6bh zHGHc#5k!-{&Oc3@}nE*IiDQQkBy(^(+9)0SLaZgG5u zmPWB?A$&#EL<`{A$QxK^Xr@{2qcqS(4~fn%3#1iCxDo%8s@OGVYC>;R%O8~*q8#GJ zKH_*VzLKsNJ2{11?ZdDfoERopoDTg<5VSsp?7)B8)@sVi$pWi-)(s{wMeqoY7Gmc( zOtq4FMM6r%X@cs2trD3KfNV?YPu)$@>O+W|r%dkQCu3|fA&b6|73ZgW;5SsqBhFq9v>2(Oai!+pY4*JIxyw;B}LI;8yr%WSP>Kz3WuuUb89B7Y`0qOdS#{z){^@&>Rnf0k4?H`;di4LUovwTYE2wZmfgS+{Ydof z_;!yJzs9QsP-ybx0?~hO&Jy^waNOX*I5#$17qQ22;UAHJW@nEP-)Hs8$uUAgorG4o7Doet|3wQPAEJ&_sUY;wi&l3%3nNT< z-Jmkm6xyJ{Q#TMFh$J;Ef?Nq{(zfVRx++rvuj53Ks8&-zy|?5~mqS%mbXJPU>Kqp5 z1vyMG(!RzT_vs=01w~C4o9Lkn=aaui3qRYL2VVI>W5nEi>Je6Cvibd4Xq1JwgqdZ-8i^@) z1h3#^A-F`PS-Vk0INQ!0a6=A=lVJ1BV-p~KdwxR3-In~J=<__IPvNGnZ?9aQ@P|)bE0++TjDA zTpJQu#O(`BF*`YW85Ibc`Uu93!2+`;Kwd<3=fS!yBT4@xiR){UIPYR9x`|_)L~cMS z3Lcvp^=MMbsK*YQ1cdK53DDM3Q@k8!t&l&b9G*qybl&868uEBS>0}HGySd2AKH?+s zcV^r@y1yYq~2oIZyo8;i2U>P}@PR8%5QyssH_5bT3;H@`wut0(ODLzlw2NGO~9 z{qQUwLYuOu0LeNdkI~9JYJL2WKE{#2SGX(wb#wH^)6LC4J>6{G;wiIV(k3##UdY`1 zs)<|W|KI4>7G9M4xPjxV4V?W-Go(G9;07C=#UfBlHqV;~RO|Byb$Q!7?7UDr0VIx< zi`^0*5l$yfsj2xmE{@*udi_W4b}{*Dyc#3*Xdf30YM>@{m?+vx5dQD=$kp{W%&2f! z0(@F$0bsrXVJN(H-VDNjwiJPCZnIjB-L!^1XsMiLOc=m+7Cz3?YQ%(MR&@_r`Xsk^ zr2}?txPkBw3;+liBO?ojya9B2cBB=?HIMaM-DBW#t-Nw*Pf5Dk?5|m;W?74E1fHGa ztu1MkVT#E43Tz4waMwyUe~GKDZ(sw_q7ra6DxtlgRgKgxDQIh)^uxZo)3Ta~T-lVj z=_!V#I0n4L=~!9WHp2g1(PYoy!<)z^gT3u0X9>c1B<%yd%6m!?>pKZQs#6A}b9{i^ zI&-}{(khh0UF-Lg_Bl?j51@$Baeq``?ACO65&;_bOB#CA1##Bu&-KBzpR{sBfzzWr}eZ@vy4l9ROW3)F{8i>YP{mk2dk3S!lix$=fbli(MEPr(ylDv1=G5+3U8cTTRhkQGIA^8 zHQ0*_-(PQqGx>ic8~iwQl9NjE?=IPRuB7QLwM7XY^{Ts1$ra?{-h?ri z`!2L=JF*)e#BUI^8JG=c{pACZ?;UA5TJB60>xX0+37nqB&-})egY|5l$>?6 zL$|Yig_q2len;HO^Q&cm5CLywM)dY`{gzk1>Rx^TQmEFO4T=D6y zWi$&ISw#lDo570(fr7=K?*xxQpXuu!bTCAD%@BpnmQ!|~X9K#WTiA<#`NflGXJ58L z*w&umQ_`0B5ca3tL-Y~q9~=<4B|3k{_=*x>yVRo6(fBqW+&;$BVw z#T!m;t{QdvEVo3ik__Y9no*1s?kQU?Hkey6I}xUme>UiM91$fbO+&fy#a5%bM5Bcg z$&&p42l>cW_97&B^oza1JU(>xLm&AEOhSG(Y_xy?@gD`pPm;#5$(?R?g%I(63mFAO zF%)>CoZ)d$E&q^9!%R9bSDJWU0h(?;!BLo@dX>@G6}Hg(Um7dGR@sQ%&VPYRS;`w^ zmA0E#w#TXB70Z`nX1y~leqNrQ%RsVjXT! zGE&U0FVoG@zuE|{oVn3MOw=>JqC@+94hBQwk1r)TL_&DZ(hbyHRU#gtK&G#-_h(ji-eWJo*akqto!&E;A4#|yt66WNX6b@l#QwG zv8h2l`-CMFP3`D@xb?kzZ=Stnh=y@PDk-8`Br&3dRGk0(wig#f#PGr$GuJ>8-`vMI zmp7s&5)$A4)FVeN1}8OEKSY(iY>N2X2ve(N^f`TXeFn3QhHj_ptr{)5xdSd0OLZKF zu&P1>73fw2*AuqMK0qsgvVoh@bFAsIv|5ySTw))OIfh?QLo?a0CM{HlmkNi*#xpPy zXC5@c?NDjTiG55+JP(~q$U`mJvMxuD_ED z;(DIeQA%M2F(wtDJO+X0OhFUrQ(A`odPf^C?T2Qn+Y!+66@>K`NT$$yIpIGAluIOm zLakQeTZS}1x{{gr#vng^N|qifR#@PI0$98GVlRSGpI%dxVm}@{MGoDn9mG)Nf~l0J zVc~CCA9)~x@4_$$fv<5)mxhH%eml(_IBPMRUs`qtlbJ+FNtd`s;BgkbVE=2Y%4RWY zFAxfj?0eH$XE3~My0;y%y`g01`WwEX!jCCcUDB z1ZQ+fiO{F~U{^9kk|9ZDCRfWh22@5j1x+Em-J2ndc%<3Slbu5Z;J2w>`dVZleO6a_ z3n$3*$}Wvg^K=BuH1Q~WPinUF-8alL)+pl=yG;9`!{&h$D{O!aw`&HtZ8hWz) z)wR;IN_fPDAV44in3Ptp{C?OOti3udO5P@sA#yzmL8WS{S+ zA&agnH*xF0)eUlRo;|%KQ}kjsUw)IKpb!P+&G~_#3EvXl>nCeHz?S(VA3E} zMs}>@ZlGT1@lx%5P8m4Lz` zNRLAwlcG`~o}wZUPQsh*eW^onu_Wc?4Ypf8@f;JKBL%ijaWxW8&AXu7OrcUWhzq7t z-gFYY^*yC0@99l(0fafnKN4&&>FzOKF!G6{y;724s++($qC45OhL zlh9+SCpBS<4~atpJ8Ul&g>d_zu|Zq(AiCSGbCd)DXF4WM3Y2AD|IvPt*N2WaOK0pO z9kx!LGMOyko`c!mW@n5vEmM*zd-44o&%Ta^aGb@X(3^s&y6o2Kn$SzQx!;|>UpTvU z$Q=xFJkya2UxIv8`mwYTWPQh+ZZWjz7`K#oJaKB!FPLDj{}_A}si?XMQBaoFKr5nci}5zJk!9>+_(yO-t^Y1cyYR56Udoa-!mD@Ik863zb4$6=Sfi zV~1MUKSSP7P$1q6x}A#vkmA83Y62~X;Uhb`CQigXvIrQ^uUgSJ5xK8)uZF>0w21-jBz{ zLJ1-%$^P!a|Igm{f46lT`TnlIg5|!PM<&#UYh3U&=e(c zBatde#c{ITzx{pY8!*7Vm!xbbOxbAF_@Uy*th6hLYj=u3-%!=sK z^U^CfqboE+V<}sYD6VT~dn=_-4jM z>Ky+C>m`DkvpY~MA4sngRTWjAscQ|10ESzSYYogV4dJ9^MF)I^dg(7m0s&2 z@cfd4c8HoPtWAi*r02Z%|CKBP+8?;serhdp^JE~v5$dPlLE!sL;_;5C7ZvcGn$E}n z^%HK53M-4nAZH1LJhE)mPc1P_S1~e+l=XB+^Vwq2kek5JDZc5Q!yXUKWroMqK&3kQ zxeh>*k$a55tG=txzZyiFh;)`8VW%6fD-0%d0;=W^7h~m9a^3@K6oHI1X^i&!TqkKS zx3~yD<7%SCmX{LhH}({mk4NTDhTUncwM&K>;l_i*BV69(m@s^v8%li9`MsD)c&{sH zteb?xX(Q_hrUBS@LMdfKp_S?h5>ltxpWQDa+1Op$dYX}n23_axeqpoc%~=U@F-fj9 zwhnDJY~1t}qI<*9){Jx)y_?VYYW(^YqBwd)ui=U^ob!F>(D^FNb^CEGtfWn;-0c(M z*INa>y8vU^O-0-8?Xrc#FC5E}NhRNb+a0LRBRw1lz81zr%uFgPM|wD1+(U0mnV6k^ zcR5epWgYs#evQenSMX84G1w%Ps( z&>vj3KDgX&oE$d`>7xFDG$?hGAig<=F@Y5$NO&ngya0PY+_a6%Czf2lB4gc0EXvqG zSd?$dKS_BiA*^{yj~I!f?=*GaM$o>1o{ID@<3`0}OG;W8F5ZF=w>albhu^6rAYs#` zXiKEy(OnM*8s~AoLl8vHc~SjwWInSPS6u{W9ahm zL<04J$5**Glqc8KCm9PPoGY_80WKc)z|;}^zCfh#@pL>N-d2!xQ(taZFjygELJjBn z-9BAIvNI<#9gvlVlE~8lCCSG?S%n%-Siy8J(o}x4lEH<5D`Lu_k22E1Zfn?_BTJH!34+5E`c18YeEjL9&W!^b zkF9zl*2Nlg(r@g>lT+e9%NsaujYLfUuJUMvk=1KCWWAh&_?Spa4V$JSL=rn>=@Au* zzZ;la$w-Kl6**n}M2F$78kxQS53Z+eQSq`nvTZ5%#|Yhm zBHN$PT{dz@W&+CWAuxaN|H;=EzuI_N8R}gTM^PA}3&1KAcLJ;OybPC{svzp2m%^<9 z%2aEJjz~pZuXA?2r8Rfb9rWd{DHo7vnS#XF>BKMC+H6vFxUjyQ4{xw(TwNOT2e;ZY zR~|GfB$+x%m&7>t^ z`iNY5bh6cIa_MXtXlE;t#K6LFDcaivQ5BVF3`}qIk1~viB+gRup#t(VE#}y*Zf=5H z#x7`F;^oYa#2ta1B4x*COKZu#BvcVJ3<^|tRrzejd(&(90^?0SoaEp^bUfZ&YSoA%DY_D^)itPPkrRANhN`Pa&r8gZ|%D|*zTYO@ekU{m6y5mmZHCC0Mj z^~P&WbG&LzM6+6Bm%ZrW9?r;g3vjrHGo;b?vI~aV_D=M6cAE`teXDxDUDCztGP=B= z&C4)NkB)pweQBe7s8Tzv8Z9UV$!p2dJ>y>@9E+{$l9Hy~nA=H7{rQQmp6+yt?bgT~ zlg(D9Y6=sJ3R{Gm5pk6OcaNCSlBEA0wPtMlen5)mnw`3qg%lTLGb0b;A+p>K{nRqo zvh%0c!sM5xW8WU`DugGJRd)k<#+8&@Wl?B;I>;nYAE*WQ>TntVuz!*(rLj#0VEQnl zfv4V{5Mc&tqY#%5LNW*?sLO2k^nJRYj7H?cSEnAF9`5cQ^|~jShQRntHARE8@)_wI z^DFr>GsCF&BwROVuo$GF0orc`@^u~HaA)nY{X06`q@66CpoJy4tD8z&s-%Twr*t*v zC<-aMV6>IMyZz+!2GU6UP?*Pu<1wa^G%2@~zF7-2qME~y1fVR!aQGvZ^jjdPWn3g| zXFPJ;p%C}pHwWD923rA)|GbIW{s`81E~@y!OO6SG3hP6Z+tN3+F+fnsN{IN45-2SR z&`D8hDwZYgEc_fGzxa%2z!Fe0y;=m}L;|T2T**``y?6x{g_HBoJ~p6`urzQwf=zEB z>PT?(IuwVe4=z8!R?gM~UVxw7HPEGIAutPrh%`Bgl=LrMx^NKfgG(2tjs-||W67q# zKz>GyVs*@k+1MbhnuG+gq&kB|5 zDTfC?u}zm}GvEXv-Ly?F8#Ri9SB0kXX5*w*7dq(=cAxil|Jd6*+&~`+;M_R4ha){2 z0_o}wulDg5uX>gl1a!Mx4azTZ#2uz+%`3N-q40XOIjGXEhb+GM^}8ySSXZnKJ{G2} zFJ0t{*Lz{VjNu-!;_9{6m8$E8dMm?VF2Q|Y-5ud3Hi!*o&@Px~vnQNB&PG0u7`3Uh z%wYt3-o7EqLd0|uhm&tx@p7-<%X1mo%X_%8-zQDRt5>=eGNs|&E795oZJ=YGg=xE{ z8`_|>ba3%Vcg`uul7bmPXKK1N;KMN%s@R>0bmb+&_4*Lq91LJ$8)8fciIe+&&YbocEv&`L1x zKm*NXF>x1Mf2A{^aZriaO<@xSffk)xRNyzvdv5u95Hqe{Q}vXwkT74VWj$K0@vJ3R zZPL-_noX{uB4)=c#>nCHYGyBqaJSPK7fHFGpo)RHbJjcUpAGmWDEKa3&G__|ZUE3r zD6M9v;(uw8l%X{F3N$vvy8H9`9vycE$XPW$D%xp$(EjWyR*sQs!+X?-rK-_t>3V{A zz{Ip|SY7JdkVsBAz#J~nlJ!s`-mR<{)w!geOSV(=qocjOW0=&EZoB~!`I;gfQU*VG zw0y}vq`VN|Xf@iJKC!O;cwipgfV!2*6T*c{&YYC zJ~*!K%j5BfD@&i?NlTh7GO8#o+Epc4vidbZpO1Uz=iP%IF9Ic>0S-6t4jRH{3q|t_ zXuJ`$g|=>W;u4hZ%l5`2L=+yyUCT{&d{lg~gZr76eDH`0;*Ef0zN~8wAym#^#SORG@|lNvPmQ#3 zG>Ut8zPhBe?Q)%%O!De7IEpvKPA(b3GX zm0LAm6ck0CGY|FoIMq6)f72~v-hjc-F&Llf1mM~J#>yjxf?Xr>{#TX=Un@9(Vzid< zMMubsddLLj-q}-G*i@V~Xa*6Y5&m__lC-YV-9wiFf9)`-?sm4$>G_M@U9b%?(CfM+ zilHDx8VQOii~E%2v+MWb`yv(yvl=S%ta4?Shl<+Sg7bxRewbVWdvzNO4EnU6AB_4hJ}O8z_34EK?-)-3z7QzB;afOFk7oDG*@VbLv=5+k9 z#MO-E--0bFJm1=Z;R&%lNjt$sugh^;?_7%R5y=9f<0+DbW!NzAWN7$lnRHol(fT>y z^>3EZhg%LDnWS+EVrgA4i`)XwLhjOa2@MFzs)kg!LUN48grF#pOeqjkMhqxm?U%x( z4QVXa36pFc6N#CmWBB zy1@LYb3s4S87Bhe^sWK2qnVNOPNTim9DiVbkBTvmWKa&@I7UMExb_m!?Qv7+;M zb%k3v)64qW60D%6@(nJb!Dqg{_VfNZ?j+}iK5H8|y|gPpWofv-e}3BCt8uuOZqMdd zvvDWjAS`m0ui^O-S<$2^M$}7}!?zSkfy$SycV9PDt6&@{44s@ZkpJ1B1B%^X4cba! z3-&nIq)zI_v8M_p?Vxvsy%6nSV~9^BH_B^$u-I-GW%0IA$=Q6iiSm^1Ml=+zH$@;; z>?$i17925)vjE=)u>D=hgycbPZw+)aNASYTW|Lao7RF$g4r0d`QSqV=+pdieR~qK` z_cuHi+<25%++H@qI0**9?b=+ z)H7bAYmTRx0_A>Ig}s3TgvnBmHjL!lb%YB7wBeducDS(L+$)dQy+4&oK*_c%Z0IAT zp#SIgFzf^MR`!24I6fCx0j`4c{+$J}d?l1xZDYfhej~P5k{}?k_`zje#lU%8 znwkqNAPx;ex0YwUKfUOk4@`4%bt;A(HjJ^$+gq-RHN$qWDKz;tW0W2AM$sZJJmALDAOw*!vtv&+VLNQqgGkFoIL`D2;@%n`0{0K)j{{{pa*O{%K=m{azr+hr`k3-Lt%;UcI~EACU;rv zb`oX>{-k;how{2nw)Dh9*H0Q%JAu^F3L;fulPHpG^<|es(^?9&W@_%3Y_coI8If%5 zlS{F?L-^fObO0Rh$3>+iCh__>ejNyw?xRP-pxaVPx|9LJ)SdtOcHR;?_y$kmdy#d% zbMb#4E?=)OC{Xr4m$-U2QUg5Kvr&;M6>=spIShAajCxma?wx(oV!hS-{?73McOw?q=g@WFO?dp_0&}b}ltsUG0Bg4z`(4<+_7ZJd_G}yl$wAm)qjA>zzrEex z);>e-3zjz;Pqrk@#Re|L7G`DdI2`NCmiTRUAivHA0vml^7{AokAj*gLn}fLZcrT5d zG$IbtMNGO?Caq8=$Rs=6#g>lwAMdI0!NK6^c65Kv7TyE;ZJXyew{5BP_?a0&xW67Z zn(xxqRV5{=rC0YET>kz(!pmet*`dPhQNQS3L?w|EIS7l9i!{%Z-&jsDj(=0;u=iq0 z4}TI9)mv?#eq}KgmWa~tdlME%!k$d|1ntetqoT&Vf$4FxQy22RUP4l>*fm*^Afw0* zZgR@b2&1ry%3_@lCY1PTEsCKMl)T@%#tBzPuBjS9H24%yzKdkGyirNEJ(&BFm;G@sm)8%|P0 z?l2GN>{rG?1j zUJxjk$j{zrT4>*Flr1jx_C@FBp+nq4MSZFwFQ}#pwO%~W_Vm16E5{5 z0c=_wZyWIh`}%bq$eq}MC}ur}IDWAS*r6GuXxz~qiry>~U)+S|Sv?hh8Qj6-fTR4z zT2hGioqwb^hfWnnwmR6`+1rJ^xAWo2pm(<0I~}m=Z(LF$XDIHJ~!OO+g(e^dwwd*zPxunEoQ zkgU!XkuNBa)Qj9(TB#X}Gdnqjr^{ca9CM;sxVcp>gl3E%nT%O3crTgpwCEMov;%Mz zv3d|&_7GA60k=#+%B%E}Ry6-Yp(vXFNlbZFF)UjY`>Ju+po=J#8j(Vz-t&2E?sOCH z#j3gDU+?zbW(py{uzRQ>8x+~8pR|@B3Jz>uyw1!X;S|Kj=!KDtiN%Po4xNuD%A()= zLNin|i3^N=xECUDduc;20{U<1dxu9*kwF8J%;;2?7PD}biJ4iJ1-){o0W^~)Nr|9% z7!fI!BI zNYSLlRfcD3GbxOO#6r*8f)X`+msC==HoU#R!TZm6k?JMxzQ|1!JTYvMg*lFMAZ6cO zBj^l(JzlyIjLKNg=#T6lpnnk0A|mTa)ZXsV>GLiJqse@CR&!?Z+uWEt^LQZt6SRN{ zqbIcVYT5tWHv-fd2ftuOGQI?}p_eGwi%r%KrkeFFo?QtFOL{D}9<(e)=8F`#kT)T_ z)6oqg&V612BNxD{;ea0ocbSWIfK+Or0`wY&_a+`N-6JS2d*8fKs393J=%mauJi91~ z;yA4|z0nQ1A~F%?ey#f|-5f@6vMD2hq2)JiR6cYMTkdJkg4b7}d~0zsUnN@bZf9Az zkk8X{)=&tIlR1^~@I-uocFZ^t+3ayRz*=FG`0a`shsTclopRtHkHn6eEGQO8q!KAZ z9O=znS?GH6RY{9(^s+b?DnG6!Zc2lh(u9~!1t|L@X z5{YF-tzS|plucEE49w!&Ea7l36XuTech2z~>-cK;aJ!_N*)s{W5aTfjs1S=mWbpP@ z0s*d-qnk}?8_{4Td^MZk%|V84U%_&9TJ<=9mC`!Hdxo(1*2PKXpIAqivpWdFr+6dq zJErfXvxt>_b}plocJL{z9xWVzZb04EtzBRR;Uf!cTQW{TqWUZCXwlq1Iy`tjXvX)> zSXUZOq|;>;h2_`bNjDFnSfQaPBx%@)lQy0Se=_n7L!eiQ`%@MAlB#hqs<(f%?W5LH zuuc{@Elh99inz#hARh5Qn_5S7v2(FT-4b+kzxD#AeLKE}(nS>B{kjeA(0^rf@+c)N zuUsI(DRghBh^Z_K!5HhP_3xu}1y)|6XO)$#Xr3@uwUL&Eow3_FULs^fy;_B`FrK`v zl9cSl1=wPEHNLu?T;J3NKtJ#9_l(Cdlj4d{Cn^|5gvQJMEe_2F{0xK*+4CErlqkWH zi#{Ms#B27&+p@jjj1xN#=}xb)eW)!b*@kX?KWUs6XO)=rDq3JkgQm;LDzhEO=oVK% zmx#Lau-NejR{@Scsp4WXP+u=x$BC7;xM=(3a6!+GJRoq#-RuWC6tZ?L>eKndOEMKh zkpjYONTSFIC6N4;CRbOHeY}-1sf}0WzNO#ab?D!;Om)-zbRI1<`!C0%>OEu+VeK?Q zvdgqf&`g|47gWgw%c!eryV)1_mpDqRJi$+^#;YjJrb;DV{j$UKJqQP%JkFGh)5?F! z_97*K{7pSIpxV4M!}V?y~)UMNJ1*@0Fs9s4j*L5i)qnY4IHy5dvpH;$G9^-m zUlf7CE=iNepAoY7i9N}FO=~Vb$ZieA+wIr$`_2WIGEkI)|ELGNV#A@)2&q%DaZV?< zw9x6Bmk9kpY|$=_q9qI^l;u2t;_YETnPm$B8`B`Xp5qSQNcTnS+Th6$L^z}bQ!c4; ztV@n@n#hP}$)IcXfDFMc>IieKP*AsI0y`j7;oEm;<7T*!EMkLxa_QSeWQqfp2{X>w zNdN?fO6W~;2+u7d_awE=t&ErPC5ZIRbx;t!dfy<#F8G9+wu2ZlP2={Kbwb%U7$z4C z6aJDOB;r7y$Gwvm60zfgZbjm`JC0vLpz!P&6>IDvoM*EzkfMKIgn)_sT?n=X0~WA; zCFoO$YF~n@>1C*w9G7g|Sqasmw&cCl^fFXB&ITSGR5yHL`pArZH1z>HeD;%Z)he(T zTxD>|%77GgmR|17h%Ytka(&E&PBjS=p4ogx!EbjhlQ0yI>L!uer3=fPrx-k}wi`O&90L0R3pZ1RqcY7hk zR#lPg=pWIfT<07GP`M#2>540xe~X~8k`|xOILt<~0Cett`vTzNK(O!W?w+^lCBNQD z@Aw-7ZlOU|0zc6ZIU4}iNFoMa0zllpb_1@U-F*%)a{GFd-b=8NBMg9MMtA_!W`5c% zOqA(&#D|0tTM^e0hj}aCV4I9zf;RP5!fS)Z0lglI#W7zq+85J>X|puf(si9P&VyeQ zG+kvYnu=!E7$B4-Yf?St*>qNH znwkM4WFikiO1XChwPkhJV}`9L8A1TP79D~DAq3Orgdm0!gllzET|slaP#YZ9HRs>N zGC7-aJ@y4Si}=~Yay9&2lJRtY9H~j?m~)U(h78vFfWTDpfHtd0Mq-8Rl%U$NcR>)j zoggT;LbvQkv1PJR{}x+O5{F*}9BU{B=NOJP{I_5)GZItAv$ui-vLD5k@@x51YzfQd ze+#@yP2n^UlZgHhIH5+sQ(E8pn{2V7%HwuBHe^xq+G!aIzCcJ~6+pBPp7~oo93#bs zDcDzf?))qRY!_tSFzBW;at4M*E2Dt2YLv_fhAOy{XYwqAzN)AHQ2cULlTm``h72E)+Si|Sda!#HhyfpucJgw+ZKp4?u4r5Q0oUpXVH z?3E@&)62^-tdZ3-&C2Pc^;TPTE4!?kWU0%VUhc9kS=Zr6%T3a6{tgr4e7-c&&p z-m7e9CDKG83~Cf3seK`-WsC{uS_zPK!W(-x|S_lC=1TeimV;f^LG+6g{cRe0BoKUT}WrRF;8;okTb4~V;C0lJ=fJe+?& zp0`+0VJEU+A>&?a6tfjoVPOosiyfNSw42)c3Ho-p%Tn@<>U5}pH~KE1@abI@*I#GN zVu-qjyW=6ibpdts)I&za9doJXoN%}&kw;tG;RzjqQ9ea*7dr$#Mn^7P6!~=L#q@GM zd__N@SCKNwC`&pVPA;UwT%bIDGH04f#6^6wbPjqbhv+gJAL4)Tuiz+5mprEp^o@E| z5Dw?#26XZU&e7rAqEQ8Y7wo{i57W`YC_2FLGdiyc^d(0EdM+ z(?oyQ(cLi)W*kyBr@GpTzZA{!J)yOeD9lw+PET5kyGckl4UIct(`qHLDqO!& zmXu^O%|`TtVQK3^WK6K+R7H%f3ME$D4|&fPG9x2|U@d-rQamZ1{;hZ#em!Y_T72%} zmRFGrROA={^5R~ zr+K@&MC84`sL^+sKzFkv01I4*Qf)vQ;cJ_-L=tB91?Bs3qQlk~_^ zUs`j02s{)t2`+?d!El<;sOGNNf@th~@{@U?B!%T#f?}L1jp26`11OSA3gmJ16bm8j5ci~ z(Bs^H^mKbmsZu8PT)35~Pl5sH&mZHsJ2qLvVX0OL%!;y+kCkfWY;GecEE1|FGB#9Z zm^F}ThHt{|t+&1K}{rG*o#aS7_x>|b@&i)bIRb%g9^qZ#%$ zvUL0R3*#Anu_?2XD!NU}>Juj2bIBDJ#@F*P-gsf~uCv&_ zeMT`AC>`Rn$y)|K1IN{x4qbBMhOAY(dxafYdd-FLF+k3T32!Yv`@@smrbMN_(9I{q z`k4O3`&V|3j|tB3)5*Sf>}t-q<Qzgegy`(Ks)N7E#qJ1`4#E61zr#7OcRp;&hC zg7KEi>g__gT-YzT)=p*xLF+sb?`dQ3KMW_g3C_28UY;V=qjZ-0SdGTQFU26v$SVnyF`KcrnoJl%)!P(9KK5u(1$dWGw`5O9 zer5pA_p}f9=x+94{u-OR#_$^FyR?j+K7o3M_nortY3H6kq24LcMWc=6)=mq^D zQ|Btzs@&|6V6r+7kwgqA>z@eHAj7zZpDfKYaA@}vxc`c2t>m-A%AosEK7)@^!~~F^ z^L((lDEEyI%FuQPH?p}M`XRl;FAI(V){Q>W=4bSMA#DdH<60ComZisiz-3`f9(9IX^E8}VcRIX%y_hUotdEGfkN-8g9garh z`{hs2#~1oiCmo{cu}YFjohH3qHfD5zYd*WBgzc>r<#YqbWEK4D4t5Wtlx_FtFDFX^ zME%xN!19Z#pJVH{svQF&(+x?+*n5U;?|4qYrNI>tVo?QQsO&O|2*#q7)R(nFG;lI| zKF0IN(ngqr^qtez&xoM8Y}>P^?e5{(=~4Hj2f4&%gGW{?QfSY6@a*rOBGw59QD)^#Nkcolt;TYh-x z6=xPB5HQUe?qCxH%|f3nb9JAzeAXwc>mVlZvOXQb_-ZYOa@r}Mw^6Trg_>^pBB8yC z`QvgDBGD$WIG#Z1m`wxHe3>wlu~a?we8sGv(qshUi|X9QOlpCb9n*{QM2VBdDJ0UD z4~qxB0^0fbvnR^0E-sk;wmz4k2utm7m9-}V4HaYI8x4p`?C- z&RQ7V<7Yh$>nYtA7@XV`iQ?1oem7F1?i=$ekz_~J&(2!J5TdOPd_I0uA}dx6NP2cg zXr@So!foYSgBMEDo<$fXGcPW-`!>mhljOJ3P6z~OytiXgAIKbB2H>+>2{coMxP)4t z_0G{5Jqz9vWdvbX*)om2Zft0~fZ^D+k0LzO%s=acC!awP_YG9lN)YnXcs>&@bX^L4 z_vhYO-}L$!g2jO2NgwX;c(sJ>YnN*s16h7>g|Dbg0>Lbr5pEiWLs5dr@Sirq7cE!>}X`>m1eE=S~?z;7Q^3Weis;5? zE$dKGF*c0f^WU%r7pz`{PJ{-CazbZIN<~ej7&w?DJXeQ^H!x1d=UJ6fUi=s7`r!U- zE+$L<7Y8QpnSlbg-pAu9)2&4BDX0oI_1NuOEL$@VMRN|DRnlF0xxEnKR!&b`t2>nC zzD>H{xe&=Rp~oSttp?;m$V{b=CHhI8={)gTj{2U3@027&z@hT=;%5R_awX3!)QTnv z?~Rb$Tv0I6q4M&R?EbP3k7;dt9!eXd$7b9xwHXa;t|3Y#Q4KO3{i}&$0fPfJo7g1L z0;f@{X|OfRv2wVIYVU-`E?P{Hn9`0N6Y61p)v9S@73k?;uCXD0C-oLvns zea~qBhSM7f&1lEHnQJt-e?=9?6&|_lK?Ht)Oprc-!gu-x%LN?r{znaQ%1j8`U=0a4 zSDFYg=QHW8MFAjdz@1nb`YGGpNYv7dYC^Zx;H!OWu+Q9y(2uEV8Jr4!>}Sl^A^cml zM~zB#VIs>iD&$ncEv!3a@H&ssf3xSPoG@p?RACFl!Wt*_#J0ADddJ>Qv@aJvrFPg2-x@3i}Iu`cRVc>H$e8ksXd&D*2b)6=Xr0zCO7N z$gKYiy1T`3?+9NY3Je1pP^DH|4=X6QOEQ`gPz*8Xsv{NWOe-qWNhauFIQqdvrNw0L zryuR4%N+ub@Hae|Ho8qDvxieoNUdE$DvR(?;COs{8A{Lq%SB|g>$IDwD2USK#(;Y` z+Mj~Q;npGzw{lMShC1Q;PY>ebw^AP%`CGpAS84 zbJf}H_s{kaj(i~J`*hC~CNTkA4n|*-z9WR2nqDz&){0DkNi1eez~Fb%{~)SpW-H}J z$ptV@lE*epxc8>iNso^KNC6U40f2Eio?PND2@4GnQ{P94DO%z87@)*SXZOa$;Zb}b z*~gI`9!>hub9(9sDetZ+-@bHj%!!{QJ?=)B@;P>*1ROXO)hVtU6iyXaLth^&qCuEw zh!*^b6$@*|FN6etJk!$WOi1&&qOa#El&LJ$m=1{2!)l4^q;iFDeWpI4u-x5O5&IMI zHTdWN?^Hq*^2jWHrhMqSwnTYvLLMJ2%19!OHfwRzSUkW}WFav}c@X0FSBVtF^p@V7 zFK73%gguJzWGfzb;lN)!Hy3p(MEV*lDVf}Gl~pB0If|lp(?$8#Whc{T>KDZaR7Cp zg}j85v%aFr&@^3i1~DZvRu+X%0{+<$`vOu`TT&_$ zN74SMjuzG(9U3XRq3=Xfmak(IPmoX|8C`t3$OKDq`d-%~M|0 z#7&F_KRV0~Em~0sRX?<(etCvQgr=N>wlJCL8Ws|qsQ))VxeTs}XIK9lItMLyW{8r| zmA+X@vyd*sT)|c^X&XPOntmkR3i|-smKm^hKEIazY7PB zJ?a1IE=oxjek@xek~*E{9*h=ErrOm&vJQGw0}98zZ_@Ixo*ypyKOn4^C`+qByZ-6$ zrM%gmNvMT+k}M$*hy&_AKapKZ2bs-Yk1H~F;l+bkrHlm^fNOOhv*Bm0`LO>==?owI zxg_Sj$k1hhFmtmjQY{x=89N)(!v~~xl8-p-ND zw-}vSQKOFiT?ZtFj5Q;oY@jNRV3UOwh{SPP07)pT%t5r4G++X`O38fWjsh)veZ;}P zsJAwy8wi!OWX-~>D|~5y|0!`n9mATF2f@7WR6Di+t4orz>XqAUXl{FyfKX$5SH&Cx zo6t_AstBKwVOGE6G2W-OS`5vR!Wmt7I=9+S2BBi4)v8 zdEL@cZfpDFr=QwAz`+#$TUoXW>cRYBI>pIRr$vQ6)^@f(`GeKvBY;eQCR8?{2*UxS z5&A+-s<@i)upC3wU(ecJVYXISY}?0|Ol8<}u2-|M;1YZw1odajX6-;+k;FGLi8Kqb3>>W7TC}{D#h&?fq2t(8*H4KkajS*S~uNq(8zWzjC!IQ8bbXE?Oz|TQ= zb7r+*mESA9@AvS_IpXJUW*N%&)V5#$rS|ZwUTw0u7HRAVua2tt`NUKldz#YM8aE^* z-OZ3cdfXFw50r2^4mA)j3+lmzD=hY_=-{r~YrcA6>q2%*Y`#E5C+tZKSh!=5A1H}i zTf+u}Z80C@Vh=nb$f-w?u>km*aVO)!l3CLSI58^wF*ZM-3r&l5BWq1GlixK{FGf0k zJcs*b!Q5x0&X^pcMJ5@ZS#(!<)+0o=%jIx5MgpBe= z7PXv2y4=|7b@w)$`D`J)XCl90CX&rnZqdk9MrR0&w!TO@n{XpL{McxZu*OnN^8LMy zI@%HGOe*09(J?GZexs9*GVH&zjXE9byCF^Hn#b$(KjF&pq{dsJjlT77Zs*Hyxg5=| zNzTDNW-`Jn_NJP1KQxkKQY4zzoU;T6ND$Ya{&ajap(E7!1W(x`*1wD>dE9HeK)8jX z`4@U9NzbEmU}SkMnm=z;>TeRfVI`yv$}|vnISf!tyP&D8fBmV*i(|MubPvS$m^<_7 zlcLFrL{rXt=*fyM2Zkgo(i$`)8X4Z3>Q3fg)6EP0ThLi^=#ayBQW{-GFSUyFO+P3C z*^31LB5WByi^#Y(l$s-uY!ABbvT5d}Wze$SIyXx_lr{ys3-UyM_YXYK*msgZm3RW8 zAz@I-sw>Unn)saVuvqL{6tYx^A&-btV z{Bmy_L3l9hcZd%WK`AmXq%*$~5nuUHhD~=rV?1U#+OaGa>9Z;Enef<%9d>YQB8^^Q z=B+W2O(6)69FD9JYZqNSviJa_rtad*qEz5w%=lHo+hY9YXPozD_hVc$yTiczX<-SL|& zTu-*Bq9vC0^C*HA2zRC{oXmJ!sHGt^slHuf73Mlwa1nKvlQe9M(INSbqXFAxcbW%e z3TQlK&sDC*7GnOD?Qv*tt)L4Jg2g9=|5n8jZWwk?IiRyi?ZfbfKNQ!Qk5I>#hf`9D z+U@X)kAg?|QW~=KR)a$X9NC3^9VsKqA&Fvs=?Ep>b@h^gJA&>tb2I+A@$AuJ#zFIkMf5MJzkKxl!vLr)CN*X`mO zU|%lY=fXd_{iU5`ba3pGHt$Y+V?`5RUF-;6&WAT(CO&UfJ>+rKSkrhyO$@XYDQ`c- z3QHPRXq8Chti^ z*0p%rKP~kXbIU@Pg$EB(tRn{xlgl9;JF=Cm7TaVjZd&LaQZ+En>Nh@C(2tp->6sWLs~oZBq$(^9nuEqE&%@i50=GR#H1ZWtvBwTue+N1(>8^W z9yuRYNp%;}gPbZR^8%__g$*SaQW&5lprkPJIO%-hUzk8%h?>==qiFx~X-W@i1Nib% zL?Oo7OK_a^J@8OgfJpGavLAjK#Gxx_-HS^_6k&gzK1KPk8R&dS@%med9GO#qa`*JX^qBt^|={myUyx8Z({TQ#MT379F=<-(x)<2poU;pu>KR8Fv(K^@o@;~&s zaSOZ_8)u5CYh5yxHwvWA9{`G2B7c6-sfSDyRR@^!Y}p$^RIZy2M$=xQelb~AE zWP;K(sr`d&9~mH{SiE>mGpD1q0?g3z6NBAbyp#0ezI&(<{n_2#Jb-rdl9@b-f>^blv7HfeyOs; zv)?^*o#xe7k?d#4mW`{1NYb8=i&CT2OA8ULa1uA3j$Sj})~u?m6sVbG0y*5WzlA6q z^i6lzb6O6|Z$v~6UynOFDiO{)XOrC1&ROrYe>UJ~g`qy`X*RvB(`t4q{+E}jXnPYF z?{;m<3bEChEgZ1`%IJA*==yStSTYwB{=$GzE}2+qjZ60afzJ!9Ipl*W^MsI5|7OmS}CSJxI3)zka_0bK*xgxr!c*6QZfm0GW^YlH|OMfm|bxwtYLwIZ;y&oih~ ztiBPBa+nyTjLb4j(vCFt4=m`fQo320nVu0Ef#=!E2puDJI%ZSd*c(1dxKs0fZQQ>o97k@29 zSyrVM7rWGi*rSsL2bnf@qUBIV+6?7|i-45Pf{VgaYINBs&EN#kJ)BJe-y&qn7%1)| z;{~V#A{}| zWG=jIeTHaz`PA5O!d!j)l<{n>IILbU8RP0z*ax|%L9z1E>G^_Nig;utFrCo6$Vd9% zli=H(p6W}eam4!w_wDk5do<2(pRyQDCrrbbknSTC+_JzXBrV&Z07Z)}`(r`e;@f}Y zG33c!@JUNUdxN<9^|>GvKyh#@hp~J>7RaNr2#92FxE#tZbAw5Oek(1bg9zDyr)0l| zm-G3zt(G`vxwZH4)9nv@|F%DRy8RuIfz~6PLI>PZiV4i|-SYwC(3q~U;xzcITiQ8c z2oWZ;s;>;C>o7y3ig6uXl;t{1C&M+RbD?ETIy8@FGm5O+7%m&CXf2>yu>5(|S!J0ct7)lTt)|69 z>fHW;1!KqS!`u)@s?w@*Obz-l5X3lI2Dj5Z9nNV+ny{Y4#NGMBgxuyde7oS8xFxeJ zIu!4UqgiAjl}wYm%OfRK0DWM0c4379gy)Dj@5NH<_X0`^>vaN&M=tCWU=CN7t~CeP z((!+@W#ni?^qO?r2T`Z=t+B6yHeI>@L++!OoK%1ZoLOV6K}_}ia57&Qk6E?&Fe+VM zg6m0V0){6es@eka9R`--6HHruIqM7bXA=3^M!tkMfk1+Tw^Is(W}DJLHk(xE1)taF zL|4 z1c61+W>q`15~tKQ1sb;s)|Zu)j1tgJoD1t94LeQ-A1XPTYC@NUHVKuAn8WPllAcj- zy&f;OL9CO7QSKt}FZO9=LoDs#tLRrelx*)F2$PA5Lm>pxqP6e)lI?A(g z_mttB^#@7WfiF8~pVAfXuX?os#b_<~~f_vEe7IzVsKCy+N0lV0nK#6x(?GUzrB zGq~z;pmeKu^5XcsO;BX2hzhWbRtp#AI{S{A)?%potWEL+#i>h2E$Hn7MjFwYNT%=mb^@Iy#i?Vt8e_7BghT0H9w zUYtqhN}TydpS`|Ev)$=sZ-MPVZL7Vnx_iCNhZ2^ru*VGBw0MtZug(U~bO~4I)B0W9 zfH+!D+Mkx_SuSPn;c-vejrFgtu+TF&*f{sEy4~a0&^nfz#FAxgR?fgH8Llr3ri(?w z%B$31Ts2&Khzw-Dp!bv8*^+od&2t?%tCW4dDuZ&9>6%VPg?U9y8gKK&m#s$9bbbo1 zEM9Y$s~mM{5L!+qTY6x=ogK|aq_1!4XZGlabfvi^xng9tFS;`<|KX3mOIYDpS0r>$#Q9OzsNhBp zy%~@EDh*~Q^VEL9{}8#Zy)-QQq7|_rcZG)96c5sVCy$7D_sMuCob&($tPPQJc{!j* zY=gqz@N5zY#!tQ+RF&PhJhEUehTf zdJ8rMy+d87-W9_l2bUgJvN-s%hFIs0%;f@KD>+yPgp{8h%5U$p&k?oyNN7T7Z^!O7 z;ak`bGA2w^!JBj~&r(GGrkE9T#rM1lcR~<2;w^ZUY^Y)|`gPnYEB_D2udusGs&@a~h*6Th_Xf;zi7xSd0SJM2 z!ZD#;y;r(6QMwS(l;5^n@qnWh3HG!^UFVHh`pROyZ5soE?ANYKS7nZSc>A{c@%PmP zw|*Z1XQOM2i12osg&JxS#lv&PnqBF?imty-$Cn6gr?l%I%*6EE;;#jKq%m1b+uNlt zV^>rQv^bd}io@t8xg5(N*?UH2XmmSU;ECam?4M7L2QzslNNkUH!yn1sbUn2lG)}2i zo-L(QxCqRhBZ=bA$_2V)a&vm>E7jtmuY!qwTav(=e!imExsVilAt0I+6e5o(0u-D@ zc=v2aA)`U(g7a2xJEScdtv(AEJCa!^B3$0a3jW*^OA*qxoST<$$@|w>>*GhZMpsqmx)`i?OLl#no;i1Dh=UbkD$rS)_;H)D<4aTszUXF2iRjAK{mDuFv@; z45Bn%qH5saFrvWDb!?9f{-Oh6d~>JcuPpw)t_icc3B4Ff#LRzO88S#q(yeKFQ)cn~ zwxB0}Hd=xQ_{DLmSE6+x;qDh9vZSC%6}f51Rc6zUG+L){3ZU*S-il?AVF3+qmy_kg zB_C*5mOj2#m8G^GUI^4JK;-J-%tT;>i`d4mVgldvB`<>uw-Ih7V^JPw$k0zTW$Z>S zlnh|V@^J_j-_seKizHYbb`Uhc1Y~Di(Liq}T%;k_7751{G6~|nS0}X3ch7pg6I8Kv zIK$2uqSg%{UDN_jN37F-KxSMRr*MjeyNDpC6~jbRJ~+=Nz2hqY@2r2c(n|ENs?RPY z&~i*8*q_aLR3g+kdZ7)hh`347P?YxLA?|lUid0Iy;ZrEqR-l^7C?JKE*H`T=cK-oA z{VgMaV*H(~D`jDA#MpvgA9YpNn}4}o0ZJJjo)!We10YsUN26*64MRx;D(6Q_Mh?5~-zdC3f5d|Bu;8IKo^rU-J(I^sz-9L72{f{A1Y zbQ4FHl`$Xx6)|b=>PCPO@Ai+64>#UJDZH|QNjoTi++xu_C9J13Xz43qSLiN7*4sB) zYLR9%e}kObXPdlqLhF#VWUYN(PeE6UHG3ykMr+nNqhX~0owxl1B~qaee?kWOqdYo& z=TX2>j0-gYp9CW*hwLY2E#&3QmqVS&q#+}iB@^a?&joCC%A^wE=M77axP~w|U)?~C zAcNKYM+ch?N1aTf_dleAtCXl;O)X^(S~Aku0`*e8K-h3E$fkH$IF*-WpdX6q`I*~m*aT-<62B2-q8qNeXikuQK8m-l|ex%6cVZUDr&^bB1R;RbM2S%(KA z=k5Yz{}%VqyLgk%7pIVUGwcV*yTjhCito_Lj1)fs1B>ypnClOl91YR!wIHdX+BVst zBmt*5kmc^_K9{U7{b@?|Gk$ieN{h*zsxGe)HJkPVS{d_~*uj)HTrYpXV|cu8p&n@^ zAq{+Srlk@M=mU5p2?YX=yQ8DmG(kqkaKd91SOSk<6-x_<)9SOM(iC$5|f4PI=9%t109#oUh5~ zsqgj1sbBkWAJJBQ&^>J&e>i>_by$D)Ri-o__w}AD)meWm!(RDT}a~VoSW$CAdova$co#l3og4;qV{GLsgmV zI`N8{v6t^|Ungi$nZYWfNdA>taMW=tH!nr=(AYa7h%CNun%<@BDpnWK8ltEd0XJ4d zLuQ4~Oiz~0tNPP-LhZ~E*HnF}{F~FN?HL(eO~U5?p_OXelbe2|>N+lamACP0_v&_t z9DtQxT_+|eq-9!vw75b3koS@SiKAFzDmsKd#sO$j_SAGIeDXph*E#DSIe1*&b-?L^+R zsZu+_g=&0P9>4!kLA7G1_c)ZotjWGm&8*|_cDlP;cjP5Jw|3ven<=3=!9tAX-GaZM z*IlqltMa~?O<#^-e;tFp&X+B6pD%hpz{*z)AhQ|fp!IW-4Ec(cLRsy@DPqxGj-QWj zL4@~E(>0PMXO3?1bjuQY^3ML`7J0bQHol(WJ(oqRc@t{DoWD^o;f*||7^a(d(&iCw zP0VgdL1Ztf=6>(bADhXw+h-Rk?k+JN6eK;a^XL9a@8YbxclhF*gBOe>&w$ACfag9H zjta6Gr1Mh-2bi3z9u{PLu6CAZo}``F*_#hvQ3G;TF`6L^I_*k|pP}XyPyU8~e#T$= z^`!me^Wro0YT&_G-)doKO%LPK;&TNdnA1?-=WL@_ozF-AHfyJb{8f>324{EjuS~6g zg!LVm@BcD=_JCbAUhBXvWikatP^y}IWHySV-R-U8?%5xE5;m8I<|B|7+8V`B-!%kf z_!27i8sjR=0=JA=;FW&C>o&v60=s$4Ld6hCK|ziTvvPo1f+m&dPRPh5=FC42Da~|4 zB8m}luzM&XD#y?Yh#Y-Ey1xO=`tz3)ewlq0#*7w~Dm}*XjzxIc#uO3p4|f}!-5~5V zS9gvW2H%kRCnHo6}R31@#hvR3cLe=GAbtz~yD(Clat)*_YYY z-1Y7L*|Wm|q`_`)fPeld?4^Dd1Sq$_+jQQa>r&?}EEmv#y^?-3#yN;|jb2O+ca%HQ z7N6@RJJydF#`(?UYPpLTTi0XC-R6C4xPb3Bz6n%ii1`Kc6`M&#$`*H|`*rQ*?KT%s z3#8pOmi;H^h)GASHxW-{Ja}z3om)LM6O)`0fkhpUJnwc+YBY7(r18Zb$Itp4# zfpLj)U_5k>9CPOoQ3dyhzTqgWEcCfOtEWiQlI~U#`%6o!h!23!6NtGavx>lt54S85 z^2)C!umEgUQH|kAUoZwt{e9I4PP$+H7Dph8Wb!Zv-)H1WYCKFA50fRux|4^wAlGL1 z3mkmI-~)wn)_94>E*ms4Cmd=0{Q2Q&V@a1`ujwF>j>HK+JLk(8kP?h_rtmWE%zn>t zbaSr*)j!DKPtE}pr)2&uSF@z=ved< zndCHhOy|5exFCI**UYxrL5W91<<+UcH9Cw!OREJYD|-Y}nx8>;9M7qS=s%TpVj`}a$O|gE-RU${tB?q<(mpA@M&)q$ z8QpLr2XYR0fwW;mLrQpKdIN!V*g2#8# zjXRo&H|hk<;l^Dc%~<~nvmMB1r$6n|HkuN!KF5e~;rIJI|AkGi(8TuEN?M_|R<|Si z$*<9pOjg#CTb2P0o$!lsWy?P7r&yR=h*AL@ef)XKe(WOE@p$?WZG>d&C(&TJA{{>0 zx)CIWGWnQdBH5XeiK&1_HhJ#|G}bq0U87c5napJL#yhv^N(Jx8A!(p?#K$#Mcabhj zwoj+cC(C0r6uJCecyR3!p@KwXJ`QQ*5?QTi1F75u`=aqLXe}JDu(b{5!-m$_x~O^5 zGhr1o+Is#(VuwowNkFX`I6WZ@MRScVS02-#vfcR{H;tLAF?8N|@(FkVNGKs@L02)g zTNci1MLhoQ*ZmW^g?`vS*;LjCji0U;@qkMLUyLI#UpL9-5vJMsdC*T$gz8+L)fQ?B z0f~Qc$4G8vITkXxYSqr7U>nmB3=(W52iu?HGP0jbSgzFp_lz~~)R~kcW6P8R-QD1R zmxRd%h%Y-&HAXfeDAD=3woolmt}W=KKMBz}wdR-XsL;&6idq<%M)zymM#m+tyFy(@ z!&jspJS^ZhrbTJ35u+nl9Kl=2qTOq!MgPv**Ls}0{c!|Mn_pDDyN@*o{qv*oRZ4at zOc`-TsuK=?S8S)ZKTf(qzQkMIudBd^)KC7v!QEvLaeJOD0X>P%cJOi#jr!b-2XOf? z?jY7CN7@YQ&DHMGn8=}p_-!k6<%>>ipH9BQSip=WO0=wE71GqmH{(T9G}bEcvI&p{ zc)^XjQg-4X9haytNh~OlY~WohqRDV@nXE`vz0AI>=einVN{q@zWZ>Se!K@;tvO%PR zDwhbOGi|~vQ>S-jJ=vVP(My{FlR;!aZD+BX(RxS@^lm9E@%UOzO@Lo3h$|Cdb@t^M z8PZtEwY(_EV#rsEsf04N%kB8`lt}JxA^?58dSvP%M~pPK>T5Sv;?bJ13f7mEm0UZ_ zGpsTr+Hn#9VLP$w@f$}{GO6OGJXv*Ol+Sa|EE=gyPgsCzd!%KngE<@D-wsD(y62z_ zaJ*R0Bzxz|KOfB`d*@YL>Vi5Oe$pw%X&~$zUhtBiYKY zVi|_=mDE>e8DUtcC*03g()(3k8GP&?Y1M}{3!&NjaXfmUwz)Yz!`Xev5(l05rRE}_AAMM*AYujWRj;*M4E4l@=As#u5kkyF>a z1SS?t4+I&zYwW>%*gztdD=CqFO=q#@Ogr#uKAtZ{;3h=5Q=1uGoC~iZOU?<0Cu_i5 zC95;AD;c;HQe=KR`Odbqp%^po2H)#)-nPapI-#5sJ$Xwj__Ovt;LdnR;3}EwD5njs z)@F)q=p}XK*W}SPifk*4FHuvIjhb6JI$l~_+p{?;tKbW&>?Rf202g$$8g19Ll<2#X z?z&_O?*;_xiy&MYUd1^7yGSS3k4zlCfkGSS0GqcU@!l)6zi4RC)FRhbVr4Zra^CLxi71MYP5w`B&Eq-G3fU4f`ZYtMr+_$w&Vq)dB-y8QT;a)1F& z80aj7E0Bl~J{mSi3fC$CQ;(*jFvPeJ(5dBCXY5doSRL}9hna1l0(#RN>_54=D}Rx^naHfqv`mN z4)p|wX`6z`;||BI@l0@!V(E(S; zM@L8E!DWG%P5-ci+Z3creH2t1D)q}Igq{NoTQ$tQ=E*ad5gaZ;FHU!}^Sv%cB zd%#J0Uj+tTS1c%_5`qh-CgGSrT{_}#=tPK+y-N@Z!uB(ejM9`0tP&g@Xu+|NQQ{~t z=-KF7Z9SDQuCk_hD??UWrHAWvX&73B_R-ARG6)jVT`tRM)BYLD9eY=(sv6#1k}CM2 z4{nu)r|%Od;ouka+^z3 zIY~T~0zvMEMtIuPL4iyR4w5_urZvg^bdEUmk<8mU%)Djg09vd?S5(C%kNPbXPF^S^ zV>a*@2|@{8kdF{adbv%VCrzU7Pxfb}ISTR~ox*#<-LI(>QKMe`ESs z8sU$_BP@6;it5GSRp4aih3zRJy-W;1%x}1`AeVpQ&N2B^y`UF(pz!lF>68cby_kyk z65nWQ^}fQHz`3#}gdG{r?VSVV#p{Z{m`*H4;fkV1;~~1WrfBcK5VIXTc~#51^WoxV zRe?RIKdbVo6GyYf%055a>#hR9EP7o#=QlI7vBsfE$x~Ri__L*C%I~_snA?9fC87s% z3jIq*klM-oGFs8Z-MB`Ff@t9fJRQAYkX8n>!##>|Sge4(S>N?tC&bjR=(%&z!^?4Y zZ=SoeVW)+lrxwZ;eP!P=+#B)A$gvHAK)wLYw$aT zA`wE}n}FaH4ow6I!Q$>qMn<|*;W>_3m)7YWNSuD)K;fF6Jf4p)$ss&Pj|ZIgvXk_A ztFEA6Lkr7CTJ{-OCqgNyQZ0ORvJgEe4Gey$sme|#i|%1wpnIs9tZS#aoTS-yTAr4$ zRPq)(xB&xbJhjt)87w;o$WOZ%M@dxvLEZ{Z%d^HjfQx!sha8=wGEKX~N(dUPBGMEo z_XuO5vL;4%P=QksvqFnLTrKCrYdnURjW@a5uW-p7jzY8faORWm#|y;67)^$^=X?o6 zXiT6lgz2pC>{GA4Qj}b4Dz&Ibc!f1U1m@sVx$@kYDrHt!C)b zQU4?lo%Jreh2DiA9A2-&TQ&K+NjB(&}6%-0g)#5T+m$19UdwZg}{t$FNX?)7AAhu zBHE+{k$>g8AXo}M9v87~<4*AhdxWwxC?0ZkOuM~N&<_??Uw3!UIVzJ6_t8DsecnG~ z|8gC-4#l_+aT3<@h#C4tsbO5w_3ne|U@7RfNc#vK5W7aLR{@GH4s|q}7Lo+Ga&@?X zMW<;~HeyQk&IX@*U@^Xmm8APG&|>T3*vLgdw~i`sKpl!4wS`mKcNEel_LnZOKrI+A zTgePNoQe_^-lJCZu6md@LQl!Y0i|XY65ax{@I2dFHWSgxN`^(I;$Dl-oizHXDKU+g z58+wV#z}vm4@8H+sGC$j>D$Aqp_=ZvlAwvCnIx1~QY)(MfstS1n8Y%x}k&I)mqHQHbvl5lGHD$Om& z!7j7~Jdetj5hay&vZ@7#xl{>aTdql{osD!5A-7xykof<_pME1~h{LpY#U)6}5vT zDHXoLoJJtl(rO|aFk%*fncA&gx}(ag3NUV{38Hql6sV%S-k7*{Ri&#<+|QXua9xUWOqgBk7`2?qe76>^12NEiNI%f+2GuR$cj5TujmB z;^Nutdi;8ChPUOpjMF5Ar^=bS^^wnx0S9K--rfE3HGD)E)~g#l+9`ZJEy`$aZR0RE zXtW$sgN<4u&C&{|h#Cf+hB+^+>9KI&Q-neezLHj_z_%2ag}6wpC7eZ{<}4?NmpT)* z`I?cQ^R{ox)!CZu85twnCrJA>$=QTRABav&_27;O2%6rlnxQ+&k%vCRPDwyslLf5f zy3b~*!8xIdM_Kv$qtr?*oy{*6_v7LBUw&~vpDkvWuNxHfvh%<1#)x)z_rJgVVDZH# zn)BrspGeVW!GMG)xiy<`_+R{qwEw{z?gw`hyw5Y6wzx1qijbG(#3${~`7g;^mbpd@ zVsJIkc|;yu2rmzxwR)dC6DuLlmQN4%L&|9dz3Ken{vMAuFQgXEitZnl!E?pc=Y{o; zL4wD<^K(4TzSxpfkMS&cr!DPw0+Keezt*X58YCguui@Hu(F=#FX1#2;7 z>&AtC3E>>O!O=bPNkr4MoMY$PG+}0#wvJV@$2F|=U2{*yKOeDxZSK%vBQ44v+%z+e zdHjID3M2@9fHF#pJW+V%0anU*F28qC08TUV@zrcH3O{cjptjhGm+J!cU2%9+5feH2 zFWaw3Tjf4r{SvFeY`VO^H&b>7^Rjl}GBX|Fw?vYOp5wdBXs(dzPi2d&^YMu4Zr+Tp zBCIu2k8CFr9HhMmC@;joiPbfx#(S$u5P+0?5E6GZAEO@aOI?*HDc|b~#Xp3upew3E zPguFu0=?J|2`zFkn_b_IaRhrhyhPYzI9?L#u`(U|A%(xj#%=a$;Y%AaSA-w5x{pyu z#0AtjiKZr^pr(~v*lp}AaS-Hz$ej7UgXR27IX^1KF7t4 zh)O9(yHtQdbJ2_y)^sN{6)ywW)w$sES>v9G6N33td#t|GJbly>Jd^Z{lGHu5Q_-!2 z#t^T+1ZTjbkzMC%@?X@nth?;VoBi%dv~eo}BX?o*ItGRFUa< zq}zNlr@`G7I_Sf-qT9*!jWE85Q{p`6qM?K=4-O|XY`p*ZVjr=p55#!|PASyEb;Z=j zWrLEu{>u1p{ax(9*B0K#*lob~)@8(rlln#qbV`OnQ{bRglz>>kID8m=PaKpH?m+A7 zjV{GgrE54Sew!2jnFip@s2=_yHL{X?nS*K4tF$86j^o+Lx8!6(ROd?xkIfSCn6GU? zm1`d2YKgDrmk@#Mq5yMs`jN>=%|`0}!}#`S_NqnaTv`xTtNs-e%q`tM$}+llcQf^H zkd9p9DZ7_2w05X^`vw=0=*yOp<%&jaUn=2oW`m-9R?cmo4W6~Eh~idEGURm4C89Dw zNLD)N9tQcOfMdePPet8Qq_H2P780LEqNw}{vjnZ`vqk0NOJ-QCv`?Vp)(W>+JAN1I z*Sm3_Yo3ii)>bi5P4kYY9)#n5X%DN`pxWlp3Vuhdg@!?S~ubDIB z(!1ocTBMJ+!^QIZ>Fm{1kAL^Zj-qsdzmzOQyS``0~Jnk;%msp;8G^*~Ga7?(3$U!u#kwSf^N4Z|WyJwQe zl*JkW{m3)|sVqF~9mq!kL|S|%9w`YT;upJVlHjMrl1Vtd$=#DR;$Dzfa~pzakEfP5 z1yz?J>KMARvD!-v0}$G}n*=Jr2<`Z}={9m=o4F{!M!7y-} zr2TD6E4NXV9CJF&C+(+g+%64vYc0UOB3}>ZWti~htzz>O0=&gExGaSnp_hPv~+*TF`NZwl#JYhN}}0URi?%NhK<^zSzA zwct50T!dPT@!%_Ce>B6L^yQ)mMXV2enQbu3Kv=o<31a~hq6KJ9loS~-(~;$2R>a<` z_AN_?_J@9~Ib(C>nUg56 zOO{n`ndlTYRDuF*~yi6I#-u0DQP}&Ay9;Uj8>ev!LJo3e}-s8IK zZTzB}W%Jh|Q(E)KYCPu>^Gg@7jP`OiUa*CJ{39Hu!%-S3P~6!hRDjmhF2LC|)(utH zl4rh#-GWAzE-yd{s~oR;g%6aV9Y^*^+S)dOTL+}&epj?d$3Mik(_(Aez??g{#G71| zHhzZzu}mp(cT?mQWvm^2Vzx*C*@@< z%lOfZU6vDRQ6+3GyRMo3mGCwGx3V4AB`(xn{PkgYDNJmWwj|OES0&Fv8{Q@jW}SmF zuA>q;pdUMU42_|+6sBo~rB!@18(u!c!3LK*oSx1WvH}K9*c6tkoZkzCW(@#-NsEl= zb4VY&^XEjDp_K5oe0i%j((`+)y`*5#yny;3x)GJ_XayL+h1a7Wsj7!ikPW$z*HD}y zG!SGS(whZOkHJ%ILR29A0HelIllGQ3lf{sPHG9VD){b7U(rbQMx)eVId zZ)yk(Ef>Y?ivAzXW)v8L97xJzU~m$&P#`l-Wa?E`i$BUf^)Sam$=p)x%(B*9E zep^0e_&Ttk9-fQpS zb3VDp>0rBi4sGt}@O+R0v}RVd41P#EUM3VnLm2Rq0+YlRd22;zBd3n`dGBcd^sIl{ zJ38v^g@(DOxLSj~(w(0@3PO;!a*rDUozEklu@+oMX=PY{hH@m&e9zLznF)t(clXgn zuKSYTP-Csp(Eyuw!m`w)afp}s`(#O{8f)=O!_Wh+ARUKBrxVQs#U}ZAO0R{c! zhy3_k?q?bk_!GaHt%?L^9g9bTWvBr1nsk&z1P~%6ZZzE8VoPS7`NDreKJfi4pVn56 zPR=b=={Ovd7xMUEt*V51SV!NaxO^aARf>2WgM76jy z{6wXWGC9`hT*Tit_?Y5{UQSI1Ue5Q!-ha_M(qX%T)q{2gN<)VH=9yK4B%b;dNiO7d zqUALhF<%~4R4xvuMmJja{;$Q!eR1+vp+&jR%d9?_rNFYJEVCm3O?5~-1NdzyyVDrV zmSATeTo#QkE_NYkHvQx_FY)s`vH3|`N23GU32Uj!F7v@+$9(b@3{x{82@;@=oss%#cA4ka_sJ;Yd}3Q~&TPd$RGx@QOu2-s+>Z_@^|1XtvI#@)NCgT%Affj625%$dCO@*aSaNT~+F$uP}{}ZHS=?{p7 zJzerZ8w{NOp)>r}@3xzSmDObM$p@FAWtm{JkkTAmBWm^{BM+Vi6^JkvUPGW&jODVi zH@t(RQRjp9)2j^nv0y{?FRlsNHAo`qb_iW4uUW-D!~z1Gtruhoq7r`Aw3qt}?8Q*6xHkY=Ig%qKX4 z$!Y9F^q&tdLA*tSzN8qDa%@CO`GlZ~`$Vk`Q`kCUdI5*M4=$^zX_u6Dc^q}(phM-1 zUqr#GGYIWi#~)8NpOqL}(-`Xxj;XLr!L9Dq03Je9dwv=Psut^5d7XOmpXz9S+Y(N& zF4((90)1dmPuerUYmgT;qRNHz>+54bUkJ`#PMlS!X>gSEn(K|hj76@<63xfspyb_O#I zE9Zo?+^;GA&EP2yF$5Pp{zAsW3e#ga%J0jhaptYUi%1||iOYC}W&N3~z>!ugnUtSS zs(r3QcF+UJx!t%it4qAZY3+LGYyl?Z61hPNBzY>9Jnk#=8Rt%(J$|88V$(EJwiqY) zv>d`u38R)-!l`8#R{i%%wnRj{5nesSGG>$9O59eKczf(7!A6;Ynj)47cZ(%LO-Ewo zMUvr^i$v<=BApBVvjN_ClMK5IiG8jkHm+criaMCf9bf9sbH$omwK47bv*YZh1?BPi zU%Uy!t$0gxrsr^RVr6{LX-Xjy@WQu2bE`HH!vvmzov&UsZk`H z_4YbAECbI8ben^HTG9-5o z&;f5FgqImFupw-!u?b$z@mCJ zf&-B!&o&b$w?n%YAv!5wG)ROj>-`KQjDI$a{&GKE*F)Q*irv@91zB*CKjn zI7lS}5}rzeLn*>sbawm4$K8{?-dRdR(PYE$-Oq5hal0iR)M>8dm5NFnAM-oHxaBR19ac0p6UtzO6rA`MYF8i@Bcnp{orEK38y zhnEey7iE1QWi0Geb!gDCa%`Z}lKDS!UhjL(ostA$2?0ek2+B1{BZW%9!#Rm8-Cdg0 z@!5DWdzga=mEV&}NcPt${u%yQfUU_{B-|0_h?0oV%J5Y8D|{lBElKRt zBsCDZSsSywh3TG;ZdNfD0okuAs!AKTUc7PJ+4$JoO9p1w_GvgBhf&Oh79iAi=pR`( zE*&d|P)_NTAFISUPzRFUbO9m9Vcc6W6GkR7QwNA~zACE4%)DBh+lp#2Gq0w0EFPZt zvFU(A2F6#F5eHD29Xnc?9XrI?i7Q)}XG00QlJY{aj>Stzr^HTKN}R>* zErSGGBbR2B(Lb_a%Gv*)y{~O<>p0T=u3rJe76g!b! zDu{$c#2~;0KuL@@`R(`Vx0#+f=Kvs0?QZtoR4n4mYfn#4cTZ1GPrh#Q60z}Gg62+Q z8KvvIkvNLxMq-(UM&iJm8;NDY3R@{K;Q=$k%bRd}F^~g|dj6)zSLNqcs(4g9d=!Jy z&Xum=(JKT_kHQDSb_LD3WS<0=Q%%r z7>6|$Iwq-?%g;>2L8HQWu5hz_bTpNdrH&qB9g>7&o6Z7nczV63kb+7QHy(>c{~DU? zq7VU#t9`O_Ga>k9G9=#={OL}yZ=#R`;Pu*x5wOc8$_*e8dAmFa0QwS@YC?NTuTN3o zZ7IMfa0YCd!psH^X&cET<1>y_!%+OCwO9%uzkxZuEUSe;ze>#Wz^DI zx4&)dpX;6JW?E56%@}rV8?ee2CoyeW)9cGO2%0^^`%F9rc5ap;qvT) zB`CoOk;hQ$AcF9#Odd~g6osymT41FGa?Gf7M!m@aP8{0f(ly~}AICYB7XDe+f(!%> zU!>eJxvku>#su zr-uLFr6ayM!aa*Kr`yE1iD$(6t{f9+hQ}4E;qkYC#P=-Z+1#n*KQ86(tq1M961LUc zm9n+!*6NN!Gp5+wqm?M7Py4*}C~3l#RQ+|axT=d^9U}ZHDw46mHem!qf5&e>k4@Nv z<3j6!8^2z_<)kw#-h|yMq#IM?<2U+)+Y~m>g=3oq*RC+3GL|40cj<9Bjmcd%8nI5T z;JJ8Jn*GQ_qrzd~-gHT9ZG_`76SI)VtbKFyB>4Gr5XFRiC+nA4vs+vIEuxWsnnYwi z{U89LxG9Fys0_Fd=p4v#d?qq*N>3ngDn0VtDorzlywfzJIFbaaEHwfoyhTerD!^+% zBJ^LN1u+~zBFgZC0m7+aYJ;e&2W{-d_;PH4m^%EC3Uih)!Dvkuv2`_E*A0CR^nD4^ zI_V(k2$!Bjd$E%x^-^A`X9JW}nLXHPGC3Hc2nC(*+>Z^%74V>ShGC7+9V^&|Mk*tJYg&h!s1gyG9$Dviq(2 zx6)KdU*FXJWQ3<8`QJ)2n!3Ib9fADWdLVzhXBXqqTNVLAc#*>6psmW9KIB5xs&2^0 zlC7iKN^3d-WXkl+G`RIZ6a08ANgO(Md7u`QksO__7hN*;_i?tz6zsh=LEs>Ru31&l zBYAu)e`|gI1&(p7d3`){0fq8%GzT-wMpy^!ngI$@w1cKGHX^f9Z%uCi5NBE8@T(Jk zZ9ND-JzlHhYpTAcV;N@XV^znEgRQN-re6$LHdr0`!yL{+ioy<%X0m!LNgfN;z2lN4 zBfAyoMqQZS8ffzV! zr>RRGb6B{EO(#cpG2Y59zO9*8P?-wB@fn@!cCbq5FHS4QI3r))6bHd)x-i?;(b?Jc z70ziw0w404mI|&kT>c$xkS!;CbZ1u!PSg%u4*39=)*nqjZXd;Z(F3-OptyWxcf2nL zQEBs7AZdICO9xL4@flD&ACl?{HU!)t5&IesS*?yCMqgeTD~Y2EG}LA?$WD!n)suCp z+dCQj1$M=+qWE+c;)sz1rXpzp_YL>@&+&-%-cxBvb}3<)JW)+!J;+;Wxruy za1MM61yKjnw?lexUQVr6jTX<)zwDh5oL?7wn=9Jk;oiXq%?JJcUVm`%ms*5akJ5jF z25TB`VmCEC==Hz*1kE1o|Jk)#W9T=i4NvrLckydJejM??hNn*d^!VsxfP?*`reXAs zcd;j}(uT~Xb;e(;0agV9ElLpwp_Q!%wpw0;Kd?@s0Z@#a7=_nqmC*E0DIXpc%u-I2 zG8hO`*d;44kfJ}MsFy6` zEBVky1H!%|!Ikw|Gd*Efl2k~ip^gHYx~Rw5nqIh4T`wZZ6VQV6!l}+ssEL2EIgfvt zk%oEkDU2KE*Atv=;QXGm3-N-eX}X{qg{})_^sF|Na>Xhxqx4{CVM|3USfQAn4{3w! zPfWg}Fmzc3lb9jT?PE7lCXS9zThv|aCj@%^sqIvX&sx37q&2^uPThWC0jm#E-;7!? z!N9R>LcUlzc+fx8z&+Ph?X;x;xaBcPMGerB$?08$MRG!>g8V+tz1Zj?hIs306`V|B z|6Ooa^|?SJq|F(}HGdzoDdp*znhU;t;x519@Gstk4T@R}9K(8Kva9wLvf9>ls9v`^ z831#6|0~E4jrh+Ef$&u-Eo=%1yc!t-`Ujq?@PxWP9~GxpBdD;?@dOK2K>>?^fkgP1 z)tX>r<5mju@8@#r2V^K zW|L;`h8Jjgvk&6pJ+Q4Pjv7YO_Y|Fnl)p|J7iszj{bz`JN|%*(>Z`QI3pnE9v0^$~ z$BShY6;+kRIXf4flX?YPSL^LH-5&_A?u2gDPowVAh3xeuykvB8ci!w39VlCT`?L9* z@%in>*-+<`4)hj7La=@uvEsf{b~4191G-?Pw_$YmbJNx9R(1Nu!2+j^a;<99*5cKf zn&V4^JF#n8o3k7j%Os?&P#Ag|Fjm+HdMla|2*zmZep^ESu+<`m-h} zU~aJBe?}KdZA*)v#xKL>8t<)Gx9@NZ=ZehxAw?^8moN+OV4sCsgwhotM10H!$uL6cay%9FsMT;*$h#H_eG`u0jL#VBQlzK$y5Kk?W;(ey)WyA5G026w=#%le zNo~2;EDZ>7(5uxyC+UW-oCEC>8h9pXiDPi*$=O3}Y#&#|CAStHdCxK3RQ z$^Id>;HXkay19i!&*pW?i)Ecg6xYCxXMdTv7QRPM7zCb8b166=PCi`F9dA0+xFH=; z)h*mf;}N@^939XSVF&a_mWs1Mqfl$BqQ?czOW@YEX_NJ71{3=v3L{>CX!_&xQA?)% zc7p^{q%*>`0}U#nA802G6v#KOn!NwXbI!ruf8z|s{%Z$Kn`h%9p9TR@hh2*S0%kmw zweo4O;ee?{M-h-gum~id1}&zF3sOufm^L9yLQf@(>oNy>J;>Y?p~M&w+HJKgSfDtu z=9XR`W5^g)UxnC04|{f18BtEySM|xVgW1@a0CRmSEd0UB%9k%tx1A7q1P@tkBlIE( zMXl-a-3Yk|p&;R-^-5G4OBdCG{C0byY9Xqd3)qJAL%A(M0R<7KgV&h!DD2<3m0kEL z0zM>PWwn0Tve8c=mZGo2jm3ptO`ttWc?x$rzHJy1n{hK_T>_vTv$3b~be2SJmrDn2p079Vbo3ZfTgSp93|!Npmf#Fv$S@HX-+Ff#XvO$Ytc<|TH~&B z3QzgdP=989Gy6!jNvh5|)oB`*ZbV12+v}ix}Nnxr;}45v%2T zPT{vr(8I+2p3^s~e2mg#yjr$F+)G|@i=TH|(z<@OI<@PRnDi6GjbZ_g2wVKOKe`4M zD8Mj8^tiB55LO(Q(PT9!e)Ok0U9o!aI~IBkrgmxEZ{1bXA$x2H z+ZC@uu+E}@v4Is%P4?+7npl#s+1iU4-@n{@srNEq=F&)EJL#D9D~?iJgvo}tB0!GT zB3CThkHaZz$S9=4H+s}8j!RCg@K7Pb=Cm;qqD%U3<$S13A~{U3`KYlKX9%w`mKery z3MlwumDf;M!MOFM%8r|W71-V-w`P9S0(!VM4YlHU0vwGH!zwpUYms;}g)txN#wimz zGiWO_so2u|h~j265A3wH7SSR1R;UK^m&Z|B5<{6dh{JVglSptIV(^X z3i;bEhQ5%QBd{i8C!{GR!uKX9P{YsJ7RHG;Jk z2s+UFtpH5W#+^DBAMBLUbqJdee?$=%@6I~@v8)kEZ^u()ExDj;r%PLh-B7kWhYU(jV-EM=;Pk^v@HdG0)ZN9hb z^>rOBPRQ}P77SfQi;h=wHI9)&C7U(_O1_d_{tkM#Z2FyKA~x_8@oE@l=ZJfRhe{~8 z&r-1_=S+$;))m2mz&(SmHBcjK%PxceG=)n;vsPK=|65bu_#wRl(0Zs$TR%7oEj=)S zHZr@U=i+@C<0`0ZDoMHD#8^rl-rnT73Igb?phxMy7hj7J0)=^&dlaH&`ZCKae>_z3 z=hBwLAFH|?sKE?l=GK*vR#h=_6gZh|r|+-<#gh@3oKuY>O=hKijU#QK8>uVs_Q_4~ z+!06C<~c$^M}j%Gx72E^L8|pj!&KYlgUe0!gwxe4jN4$1Xli>wU11y4OP1t>Vlr^0 z%AmB9=JnA69%Yl+dX?apDS)dNh_h92A5mp$ZcrD{_)O#K+iWveF#JV(O5BF>5(@kx z?m$@wq4XLn3|m=30kgpn7LF)Oj9V+XLl`}+bl*wS%~e|h}1O-rk4)u=6TDvnOh z&=Cz9bbPh#a>xY2X@Xp1vZiYrBMp0kE>tp6!-cOxqwa^Kg;7%T{0yu*FMe5wAt}sH zXfd0gCNSc3g_`~ynr3hq?>NXa5NPfceUaJ@5r)0@Y>z!ZsHx%)+x&JnOUs?XEST5W zs|&jVjc(4Xi|H!ZA@nH-N^E%Dr(yrf{w~78zE@`pn=9dKdNSt)BF~NHmTCY|Uv#>u zPd#ZHGv-0EhF^16wY-V;Pkk-x-zcqZG;APT03hjZIb8hGD~Bd#@%5y9qfV}HyFeyC z2lOrde8N9Dd{@YzKv$NdJ|I-b#lqQsEG2xB%%|Ko;wVErGY(&AtfBYgs}kXVF1i=j z??&dDC8aZ>bWJ!qWV}}$TjDtYoij_-u3Mh9sow$^QU3_4N~LJ?z~V;iwL3#8(=!%- z8|^0otn)Qq9O#YsWvNawZUA`G7D3SfM+M4nO8{?nW;Zd`4_2NW%t@SR7jAF7kPIJ{ z?XgHHX-8_n9>Ca(r*Pj^3k^RK5i4+Zo`diT?AsN+uW|pF9t3a$X)gQMqrNW~j-e`z z?j9YTJU!_5275A@aa{BqZd{JcuE5z-#gm+BSXQz(6d!viTIjqh&Iw2RW9lPjrqUt= zd)3KxL7ohXBq=l}Mia%Csxbn6GgB?hI%PL6>AXg_&S^^?LI&CP4UP}n0h@MCIl7;A zGijO0QJ)fX`t{|Nieaz>-V~Wfca7S1dF7)n%U&_$7C3dLuG=V6AqyBJL$=*JGlOcDHb1fDGoW49<3={`gg$<%c`0WOJ? zb4b?#gf&m&Z@|&5abp`Y`wMnv!zOi8m%Gff(5Wdg=I&yc;jN6R@3ip_8RHOR%CcjM zVCOJ7NLB#ip{ne_!bZSgrN3bxCKnuQ_=L7iT2*opH=b|ptgBViQRJh%xwK0P9elo#$QH{ew5|-^+;#r&(x2aUy2ko3fUvA8D!XszZ z3jM|rO3)@=lnPG{TfH^mR9LC8vbHqSi(Y=Ym`dwzplS;g-qr2W%n-u!R4$W#*!)g@*ei`XnVp61+9R`@Rc6=-3lkG#ZjuZYHV-g?m=ou;AS_Qnj;e<$s z`kIi2vcebMd#kA1vWw83MJwE&Nd%ZMHKE13sLpx{_x5@+xiR$+BqW+Ak5dppLos{Q zD?fBt(%{ljH&ToyI;*W9Vo^_JHg%VAZ~dz`8*?#ImG56>{?DLSoPL2;NyYzvo?d4f zT~>nEk;Nk;*H22VzoB}KUs7$~Q4j%0D7p09p=1&Lx-*L=UXX;QH3p}4r<~a(Y6T~p5YzYVfu&l2b$2$K zBj_FWSPHf5+erzFS#a2Miht`?<9ID`e-CBT?hI6Z7hJe>lbbN{A!u}Jqwue#QI@M# zy5ELeSO{x&<7CIHg5!)l1Ga7&96YPesEM}=-wYQcm$En;!NuXmr@-xCeC86)0-9Z- zpxQl-b@b+MaKn;4rOpFU6LzOC${xM#v^h3#TP8!`l45qxy0#KM(OXl8rkME-h4_#& zhuZ3T{--V2=r`#1bkG|d>}oxXfiSX3wcWk`-pOCAp5oRjV&w>r81N*86Gr)Dvemn9 z7?zY&k#PlEDp8azRB!b)`**~9(joOD~5|-M!;M@o0NnbvTT_5eSLDwGvK* zqCV&gh-fKdrOe}2OIxo`dx!Tppwqn`qJ;BUlJM1cMUVj58w`58-xc2witeS^9z}2e z+F^V>p7wq?xko%b=^gyTr;F`?IU;agK_Q!ATZv{r2 z{qf$`^^>E+Cy0<(++T}#;1u=}!oSFz-X~X$b9%dVKrcM}&qWtr>L~tnZ#3%P{kXqY zKbfG+rt}kt-}@2i|8f$Q%nqW~H>!K5&kv68aarJfmIG?iDVuU)>u3795hmj7HbARQZHq%b&oI-2S0h)|DlK1b?#4%tZ;GJ?%XHb_p8v|zaXO) zcY7ztdxKA@N9$Jm9ua#wI63%k?>>p)Ulq5}MsbhmHDui5V*AvT7L9XmSGmTJM2A~A z%wc90%>v<9vne7JDJvUjdiY7SetPxk9_J)j1-6*%O-!r+MfdXezWGuB?nUS;rEL}e zzI{)-_(*75r19U69zmjFi);g%qy7F(DEEr@-oX~BulI=_>=`yk&;43X$ij5*=ppWR z@A%o6Pt_hdpOQDqUvUT!9$qshwL81OGv#_SIJ$$9*<7=NX;tlye6)XJr{6A7EwIr! zv<#)_ggwi~NxRF&^%Sw+?usdv-U|rXF-;2v=H&{-G%a*IpTP>eOj?R*T1YI(e;%EE zRR$cH*H&;)q3nQ#6U3Dmk()4GC4<)L5Kxx>`ZhxI4^EC=*e#jna{6eY@chucyj|Bn z9Lx@JUwgjje8uYobg;71-U;?tdr$d=#dR%OgW_4?T+`bM?2^1V+TGNfT1)?`8x6v5 zoFICWknZ>mI=>FhV7@5h+_?O!I6rY(kS}zg4zE?ek4hOVX+@X4Quxlse?ZP~t9d*SBeDxGf-dGfqLtvBzK&+u*0VL)4lu*yGs7`)z zsD;C|Qd=ZpDW47J^D%A+gCDRrTs33(u`$DL*$U)n>fKD-UG!0m*ZboMmWon?WCez2 zCp4hfnC2!lK$6bv=dK&;KhkC#sFEf6M!+k904n0Z?$_E4Sq&2*hxmcrcGW+;aGezo z0z?jJ5%iEa{HUbN{GF&aujnW^&#FU!w(Rj>&V*n+sk;xK6j%61M%vL&Xo!bth;Nvi z5YET~3hBNBo-QPQ$pNhtqc5krGLmTx{@U?K{mv`QmKWW!s98@~Dl0keo!I=QEi!%; zK$9+XOCffuf%IwegBs`mVxJjYXxJc2kgUwu>j+NS*zd|qEp$4g+o{bKy?V%frL zgx$ z{mi5C3Ec%fX9yoe>0N4x^6=f|)x}`;9aii@Z=|4sQ2uw@-9M7oCG}wIyM`j1h1pR> zA>)&`6gR`tzdm->%F5mG{Oo!%oa+qYicBUePw?g>9>LlAw!|i3dz&TQH*^9o6J~VM zKXj;;1t|5lY)yGyfEjYdlbKP;z{FPYt$qk3EalF2@%1-<+LFgF*RCqs#roNkQ8Wy# z4nk@`K@?EvsTyfz1KO#ov^8Yv3;C`#Xg}`!Wso(k$0ELMRuO3;H|zw5i5`t-5!)m_qK#o@5_o|)=pTMsI#G&iJ`Ou18`k?cPdh^?s2X-c>x z2WB31ATP8?CWV|b{CW_6nb4|g?yiiBY#J*D&C_l#sb0#0AaP*Cp;Gl&C)4lrkVmm0 ze)Xg#F7SXAj0=$G?*|VE&Q$(#ToR02va<7AnUh$TUjubqr1`VEq#&ttNeP@1%Lt3?z&^$gj)iS=A z5XKji>btzf_+nFy<2zF)%=*4=IlM?hI(;!A3@;|tcX^B9#ikmE_jLF4e6%>L@2Ylm zyGPHDP6p+#4|XTR#ey%eK|&LG5eWm|LIS~1Gap;8Qw@hF0?|(nvbuRv$+6y6%20N z1XxBE2h)que7s~~U+kYAa#IjmRHB<8ywbju;mHUxBz8smf2E4HOLLn^TNx-_9{kUi z#>1qRGeCGc^>ZazmpMO~nHnRzorjt}Ud>^40rvWZHlm2- z%hu@Qn8V_Rp(kB2>_DS3PmnKjE-n*To~S7XH$z9;qQEs_o#zV1LmJ8}7rL?Hp!&1< z@a>Y8LPHf2{ERv6!INb&jsO(dGdhp92)?JFVe8A$FV}MoMb^HMVc25QzeFHka}JqO zPH6#ldIw$p(S3GwgjWzDFj53Gx}~#*NoA=m)oysLc3=nyYWeEMa`*Mx<{r6oz17Xh zfwLpG95Q%{n(b$GOVP)v9;A&&=p5fyw-lx&L8Xix0$7x`Ycg>)5Ez@kj7U6Vly_0b z#cQe^bq8)MYIpQmauMx|=&GbtM}PvwC1gPPhd=y*h=ocC!a0>byYveOTSRT)dyg*B z#s%r&U<2&I9OjYd1WLb-@V zWxRw;#(UHmMPgzxzF_&^kw3%@l^LpD5Yvr|uWC&A7yqlxELf%Blua7=^JxBNhWWnf zgkKy8QCousfb3D3W)fX)Z_@ez(Jp3aV*quia~O3Y`o_r$X-f1{JIj@&tOD;;vb5j* zVOxM9Bz2wc6&vSj@iWxeV5-QfHbWeDightPyD1K5=dei9+E9q+RDkl!P>8_!>YGQr zp1Pb*waQqP3UGS&GBnri9d3foXQUQ_jyqFfT++#ur6rO}NoEMvHixCITox||4Bw0= zL<4SvzDREtg3b9%Mk-1Aq{`Mm%QkNxHusiM;D(H~}MIGLR!@aWe8@w5Z z`>@mHXNYmzn&L2l-p?7HpSK`(mLJAvqi*X5s2r{9t8;KC>ZnE6>oj~EzGv6(F36WR zLeq3tljivw95>MZRD@Cl2YNhq0S27+IWSWh4ciwvj!^guq1s7e6sXJ+j#{lNSs1Hc z2?pVjtx9LpqmXW?J7h>)KD+`!fhjt%3qKmX=N`KDc+x|&AoeWOg`*+7m9&lp)RK*( zxNwm;0ud>*f~82Fu{a*e4DgdG5x@K>OMiP;h*Lq2<%5{l9Mk=C3G{rp&<*xY$9~v2 zRISG2%+nU}=7nr=qPDH(6suR3p$2Qh+V!TloJAx?Bo9E6IL}dH_ESaVEEa^_fUXQk zU%VF6v1YA588aYHP02hvn}YACR>sExg)ZSIfpIxzY=MN5&pJv$Z)3edM#| z;arE1-$Z$S-DOnx@KNp9@+}E(nN*~DgGa?}iwhD<;yr=GXHz%3aFXCx)9goaFg+iA z)V|I%m$WeWT}e?9s+PsYOa9l-UsHc^6CHkOUvc{#a@IBoN(zND`U%a|LV}8Jd+zYm zwM~6fHC@_Of{jd|wkl2O44Cckzwqc9y&ZonPOsnMk75Ao&gdg4LZgp@V@{BgrTlT} zQni#nYcWN7HOJ!Y$RF)ejcSs%wh>4sHlvA^Q<~U7l3}C9LA5!?C|DRr80CiG9x(hM z>KR#P3P~j0jn+M6l$QFB>)|;SkXPF5|foPl)R1OWJR^>&W`D1Zo}{u(bK=tDohihPM-V66hhc=AFT{3SLvce*}|WDF1cV znil%v5xW@qVipF}r|JIClC*LPpC*3!m8kf~WKZIzclJRUk^cT{Bw913c9gPFp&9>T)=q7WNa->K?@3W&BA>ihaRdABc)yj~{*Y z2geMOEvB6S^zcxpRcT#bFZlV=Z_5^{ekMCeIt`$WCG9WYt#AsB9N=#S|fF!YMz_^e7dnT ze;5s~W>YzB;eTme$`5#PKN<4_A8-}oD<+6ZS-+V;C7MWZcB|S{P>lH`1H{-MgfGb#~(KsMNAHl>@V!{t!qMEpiq0E4GI zCcX+sgpy|PZT(zI!rJKrl4a%i}nK>V&+H=-j_h86s| zcFiMVEFbrzY9T`3b>cY?!JRKo7r@upiwq4F<55X{O*!zX=`kD=)z^z>I5l-+CL~i` z{n-=;*P}U~a;5LK+G=M4%(N6PPAdcl^zZQ>{x47aG9d>?6VYQp*W^nfAEa5!udt8T z?dE~wrt-uOqwe)4xIQ{O6^Q|3tTl`I&%JNMzL)NuTvX-#}4{!;|=dBf4*Xhv+ z0bdPX!W?C{%*PZw8}iM54m9%J>-ij3dbRP1s~q|7hxd=PG2i|RB)fETR7ra4nIzK+=A_^gUgZT$nR;f>)LHpO z_Yof3JH&%0a>~+W!m|%DLWCTQ4F|V{4H8r~!MTE)QoF~9gN^B^&CAH=;`vfW2xgYF z8<{0HM8;7i70jeZ2cP=zyiBBkQbjZ2hek;IRM&MzYjpESTHqB>*QG$pI8Qo$dp(+v zT&vbW&-=)Pwt&6`N-8{=eW)pXu%NOpW*?HWaKLITy9-~aG4^092S$zZnF#d^FnM_N z8!Zc+I;6B)9c%QnEzA}slXOm$2F!J(0WOsTJE#9-h?DW#(bW>~rqZA78t8$R7sx!G z!OM*-+zxjotlOA&L3htU7s1$l=y?Ta!p_lN#Px#+M z{EGnH)I(RE)8(JfuBQu0Ji(*Z@adC}C$q~p`jeN~WQ4<_%6jPkz9$H)BX7Pg9Z(Q1Qbv}J+1xj=?i zFMq`iWUT-<@qg%yXG=YsyMt+qA4Z1o#86d~%ZWO!pD_>;ST*f(7g&UU@d+J^3I(pD z4{MOLI85SVbS4%68hkvy*Y4cXJ|AAvw^K)L10fwSwj{ zyUG=pN=xn2!O`(CU7QhrNv1irqmmK>Q;^<_qvK~b0_)p0Jj6xzR1GJJtd)FvqlUW- z_GC^M&?y6#h9{HLvx^a&C`R+0-N|S;$BL#w=cC#kPLvr?DJVTtcIZdFkW8xr^L7`kf;hK zyRRaWRtG^OEGhum97(X7UL0@S>S!$J0pBBVi{0?hWua}Ix&mOovIy6W9YGL}c8mgm z&TNR(zA9=3{$i(uCZM{XxDFk`zF)`2nVGBTqrODgtc2Uv7 zQ^2;W1stfg6Qg_RV2P~oDmoqL@ph_nFl9k$h+`?8Ty_z?s$YsfDy$dGBQwK=@Y86t z;BR-zru4?Gho^Sx7)SICF!mu3@dREv$1C9+n8tA2ya)~R%*nMTyG7$~*qC{gft0-* z1e~QRLP~w;8^jY*$b`zp`epx1IkMmt3n)SvLOR*eLPA|7$PZdP_MgsIzgDn-5g%PG z_@Hi);v|mTL=6;#@^o+T8h7o;srp#%D45ylf7!&{cBcm9?7%+An5^z+qrQ^O3aZu4 zOB$BK5qQy8Z50~0@R0RkUs#)=1<87rHpVw8!N-FIk_hsI#Ju8Z))!1knX|5k*LE5pjjYQ<8^{ zog~c+)`V=r*LPhQGbbAD;yt71P}+5i#al#u+~BUw|C+S_Z1(_RJkmZTB+5x|cMq30 zcE9__7SwK2%Lu3p5g{((X2M<%78ra`OG_XKqMY^~!)Yrvs^mnsn(rUI8l{Cs=p{ln zLJr#ixSEN<$9WF&l$2{*dsw1J*UO`~WTsHFlW;U=^3B(toWKM`S;QGd2=g~!>c<~{ zG~}(r5edq>kztII^Dlv_2(F>#zZ6?Nyd5z3hL?>kHov8-*UZ!^VfL9b^L2$cUcLbT zX+cZ6%p;_`yRAO1+xjL!6ZSi{=)5w!W&tX3MKg+u1%XQ>q*F9%gc10+E5O?m_Z_Xp z#Eyv3r&r>(>eb~aHt6aS76P4!#|?x@fVili8^a4}pWxcAxG>%tEa?WJGK0@&6FTIs zp&E)M)tm#vXO+-;^Eu8kkh7W`n4OyyHVp87*u;vSZFl7{3DswQdBbX1q;Y{SoJ^J_ zJc9O~AH6)?>xNxY*8&@dcT#3^)$}&2$PY{0vQBuv|$}^ zPMn?6%S;h|;X#hgQn)OK(`%HrxJ+V!3{+3{wqEX}sVU^}FTzAdYHek&1DT zUc0x4&BdMH^kgptYT#epaB||6gr#h4vkRdQmgVwGsU-(DzvHdcJVe}3Ie3n8)q|R> zJ1vVdormX3Gsh3)lut(&L;2-4KMYA_Cz~BR;(J5F7G??z&3*H9FOicJN-s*f;^nXI zL#|1x-eeAWI6yi3B^J_V&5-dyywW7U29(cZ&4qb?h$EHMBuJw<(iK*h$XKdWH!^c} zlKE5AMNWx@plNY+=7nU0(itN(+DE0o2ocY?B$BHW*!r*kh?Y{xFxM^`zNB=yRXC$Q z{vmn|^33Gel=8w_Tv;2dwUb*L87yZK|I*d1T@-HZZ?qx?c=Dx&s*Q?e4ZviSQMo!! z$D7x*FbFTy-s2LBPZ&_mK4MRspEW|Y6i-?A8l zR)dNW2_qI%N)2W7fGB*|j8(aHWw`mM)XR!D@QQlAi7o7^5>n+QZu-Ovjj(&cKfxlB zPFYP4UyBQPW+38^wmO}QKSE4kSp-LfYD%wHllH78?X;S9T2uGQVYS@uNlZf&A&O08 zkiH>j34F@T+%muhdkkbo55gMR8-JR%3&Jj)eCfHV_DP6jLr{p#AvP5vt&k_vEQ0Xs zPDqxc?GLy*@_9%j_jBPvvi!@C^XkLTn)8%Gfk^hNHih(PTh0%|`2{wfC6##9{vcl= z7m#Dtts-Ac2zho#PpjJ9ypYc_W-dZB1a|_I$-xpZhPv7CR0L zs^;pN-ToQC89lK#vCkiV>`fqTr%5Te5{%!B=B47ES-Jjx)rKP#WoiY`L^Ik>5q;-{ zPzsmhir@ZmTasjr^1om5Jc@cS)y1lREelMQv18EY{fqwueRU2FX} z08h3E1zmW}l>cl7HW&SG09w~$Agz2U+XBwgyFGjKSI$s~;fG&3;_wZ|?|y48$e4)2 zbUWsM*yjiVvHH@foRJeL)trnoaTV1&4AYxlPfz%S@UlU`V8B|IgfNIas2iT+31G(o z{ z>;-I)2m^>i$%ol{7V~<~G?#i^!-LH#53X<*md2QSoh?=p+j8y|7wXn;l{bSUJZJb+ zUtXJv!YF@quxkcNHAd&NJB@O^s9cBUYkgpoG&gstzl9hy%h9_Tf_IX8@g@1s(3Q)O zxY&vUJ19yfl;ho~Yyxnm!UUb$rBCyKpEk+&X~--rz1$*bND;SCw*C70_7zp>pZ z>!6~?n9>rYf1uYdRj$fJscy$Lak-5Cisy96tw{p;PnX=Te4olLM*EO7^{g(fLrHzY zy9Vh|QU*HbrS{J~Dz2Oml){#tioh%{S!G#X!qn5elnJGIDVt35Qi)}IXBtMW1)z2z z8Zk|;FQLJjf{oO&tyIakE7D?(@-`?F+yE?%50|rt_#@ufptKF!338j;L7-)Nq2!Kj znM~rQ-oPUmK7He}+d`CyaT(uqIetiwo!quDFQmAoGnNOpZN|v}Ze<2>K&ZyTR>K04 z>q!9$+L6WJ?Sx-?HFjn>i&r6w-HU%N!#6hz>+~UBjM+QM!yIdJ8tM3KhK&TpvXi*N z;tP`mHHNp(^$0#uMT~&&k`QWgw2a^Ia)=)|)!;J^di%h_BJHnl&)kp`}{ z{AIMnd~pdZVIMlxm$=XHsS;x~i#l2d02O2@IinZU@Qxb3n_{xMvN&2l8%@@f4X213 zwH!)s)Hd96gGb*QFkbNGs28|%t_5}B5IS-`YwP}WwEXiY33=)|;IQk8d&GGKqe^^e z9%vS+h~7^FCb~A?O=A)73b|F~+1@KxW1s|j}-&rN(E@gPk~m0Fqvi=-%9FQsky67sA@<&qj4mP?Drv6^h# zkibABO6=yMX|BF@lAdR?+|$Gy$CfO&gm=RPmpQlz zH(o5P>n#FP?1W!i55mtJ;+T=cjLQj-m;KXY1ZnB61$+rB^Mc$z1oY5svSyo>mQYoW zXt^m^yjGlKgyk_6sY_E!t3>prY2D%VTR{14fgKR;T87A{S$brNs{-^f#NaH}IH>{% zK(NJk{H_;ttLyE2cG+TVgo^>m)xGrAn_wSGfkB&6baf5*@!u*worx1cdYC9rExMq? zoMxyG={&lK{E%M04nJuE!iU7~8yKnNG5ngnl>@p%Q>~W=Pw7lU@Rs8{6r(PR@I~i+ zM9FedG9rhgNdwh#%fQD^S;&@-9PoU*oCe36XBXFogJ~LD3iMBJ+Cj)QC z8}uy}zYnZ-V%x1EYyme~!v~9W2zF{KFy|B$2ja+w6G1!tmkEb|Db4zHcz#>z z1AQ@tQK%Aawav{&x<58L0`=KSv{KsTQJH^04x$KvDmJ4kk!KY=)XVw0f3 zDm>wvzY{eVU>M$daGQbndbb;juetb5gMqU@PX_%1Q^Rt+gqOCw_F6p0HWH(h$3w;b za0R#Y*@Uo0vlG8$Zm1ETIMnuF0+5Dz4}j-qwtXpbP(U}Oc1LBt`eRapU%u6|etzW^ zZs(KbcL#V6m7l?-DqnxIT{w8G>n-0Cv_8@fxp>mSYSJ;=k83~BOzd(6#XLJ3|9?D# zHoB|v?96O;L$xs74BNIM<4iS@uJY$tGUL$>KoUAoOkT&bvINR3L6pI)q@sa6LwpOG zj>{V`(H!BFMg55D%80>2V^ma`wF9+Lw~L^zpk25%i>A&X$+sBwt!;TS|rvqf6N zA7Y^f_7Dqf${k`2>v!uxOiOt~OkYj1T#gWfiY8h79gA*BhuLa)69!$z*5GEr;z zaA;Iw4Otnjaymn1q#8;prKaDJP-A*Rr4EAmir8CNO);s75=`e#h~XBK9Mp3|L$;(GfVQS0P`aqHgt)3) zMzXA`>D6^rQ)bP=k{W``rj-?2Z9?e5;!vzf(XvH2LI?sI0#1s^Nn0Od(bQ~Huc^P; zqH_1G80oIdkpyt*$RAQ_DdZ&rLJe<)|Y|tHC}Ml!7*F`V^_bv+cTH4~t#l5#T^L$r#yKB-aIaYv>K`nG2|f z_SP6T*cjp@FuWRtvn6shnT|fFrIl<|W%;;;5Zt(pgeYo);LqAR7_m|bP-2J0f=_J2 zaEwbifEGUEw<8wXj@Tw-=OG1-Cg-I9skUcQ!srTS$i8PJ(D-mZ$_Q9q04o!*QE!9+ zHo5SLUOs*todWOxbhGJt!->O9Q3n~oTwoWEyHguYS_X!aV*SJcaKS&KZp*Ag1(g}H zb*`rBo!9-*hcpU>K7bNtS8MFZQJu7_>ob@!>3Eakme5r+zCg0*e6AEQr0ZI;BK-p9 zh3OM)eOh;}#jv~;Gzk(!0&#KnxxkepaF*El6j) z&g&ay8k4e%TQ-WhG!TH2Lt*vehWiM6bVL(p>Vtx%dG;*b#x}zc0T3%F4*@7d!_IaY zGlGGYFM-05LC>G;AiL^grt&C9ca9b5OiEQxjz~FCt8Q5$L}ffcXS7#VH7I(35K(>9NTx5oj)kN3@5!YH_>GAI zW;I>MQq2f!$g-3?yk##_>MVtGn34`j9G_-}4Z3z;mky!iw)1r* zEMx}h+P%ZWqtlni$1ncUNs1CI)is$E(Fsznfd#GzSX^ISO>U@x%J36gVnjf!QNE79 zZSXP0551b24!oN0hrR!D?}ZNA6|5h$E6_CLXx+$$Z05W;)>yQ3rWPA(%=Kffrlw;R zGKyQI+%S}HpuC+@hvboiC`cXGC1J(c$RMlLa_h?Q1{1P&MmN^MTYP!lp86!L^eS<= z=5CE=XM*~dLbXJEf*w+bxMoBr~Q!~_S^tE8j`TwiGD(oSCbGAWJi3AS0m@iCUh zvpxRUA)JZNczwq;}Qx z&e(zyNk{2VlHFR71ibOg|WXj7LFf0QbM=*r$l5b zq$s!HGu^7l3<|n(u}SL@Db~J^Lj5V}_+GfL%AyUsq1I3BFBU(w*#|p3L_&b{vHUt) z_5cZ`(9$EjHPN1#-DC_&AUX20A5Y)TI_>@amRi-gSnC4T78XtXbK-EwmjBfKU|S&5uDE4`8%;b+Ys$5e3Xd06}NZ(Bpc;3U! z|CeyQ%;59WRQiW}XtfZ^9;{yyR!iQ1j8Mt&w6>nowsjUJX-!4LaOhAI)>LDu69(7n zK-S^y0>Q72^vgg$jx53ellE^XYuaK@sano#MnS2mg~_RC%29!>>yZg~;rAl-4%Jtm z3#j~pAGa~$VEyC{d}C3TXlgti()QWetkhg)z?>qC8MhI_l|x676|!zL$b+S;JrUYiVkd85h-cK^&cvr+ER>NA{ScwXHLPkn( zOG{_+bV#yv?`6CeRpKt}&_$#Zx@dM2W{qJ5QaCG`k-vu7ot9>ePpR6G4`tEy_~uk< zPBw>sJ;K4+2)FJm_*!|(F8XwZw1cJHY)^T7XNwzzYiz< zVh^{Jmc?LZ_*cC9?MXIrsZCC#L2dOz2O-PEU~{=@*g6WGMQAB$LgTdJ2!(5xM##O@;s~{D=;_J`+01{#my=Rh6Crt{NllhUsJd!#ghC#px<~vx zWZGpp6K%>;h4Ldyt(0H4NJ0h0diai0ww|YfE~0@aWogKwSk_lLZ(9+-mCM`SI@)wB zMINHHcAbi$E4)5MH{gxZF+WBWJ+ePTJV3EG;uk9d#PT?~)KF^bnPT_;bYf6E&I+-@ zt~C&6B!oXGUn7$m4k;j*lj|uxFs@F#+U%vO%t2KNHOz<%GeHtKRBJ>H8Qo}F_X<`8 zJa_@nQOSi2q5tt2L$7@5ge zq%_N2L2YBDP**j3(NJvyy#7D z4C8psQxZ>?vn#O&=%ivJJ-eQtITN(B)wK+#^H~xdlS?gH2=?2kn&&W21i0|Fa7W`j zdxx?_l%M5w;)GSb-UUcT4SBuWj2bK8d+6d~A<c_ z%SLj63@M2@#0aPaFnrhW@x#H>kBRVszqY~VW^Ln#wCmW87I#C4hE~6R!(PT2aQc=kOzQwlU+#q?;{o$30|% zRNinT3M(X08)s8WG{zV5OhAG0s?1b6j>MRuv4|3a)(n{zF1TXAv;B9%<0UCG6aR)h)WSXxOYLqBSKCs+0F zy#Z+hz%G~JUByt-MYFD}aXcs4yu5+$k`7f<=%pa509{J265B!#31E?0&+wpsE4GZh zU}X5l`0a1t(!mvbhsF7!NSaKZYkwxt^)pKJkl!HE|2K)70n8Bdr3xwes24FFOT~H_ zqs22J;~R-mMpJ7Q;K-AiEoo6FYf`CF&CkV(miVoJf#U>Y3duli7Rf9U2D4{)XN`2$ zDnvoJRI^07PiZD7K>WWO)aP*sx>^tVP*yoMIFEB8|LmVn()S@;MSLTs@< zJ%w^)Qcf!#&7~n)P^nR(%w$OjI3dkK_2PwO>mW3&t3b2kGbXBz1*w|xvo)p+mCZpH zK7tV?G)1~HpFNDuWi&@~Sv56-gwBY%Oe(&9Y8kP?3G^V0Fgh2&P>nP?La{!_f)e2) zbU~kj8Lbx3>E~MWw0kB4!M1n=P;f|6GL0pGL_gQxkGE;BrbI!b(<>>ke<#~$;T{q9 z)>LZXky6q4joWII7EttIcw>%OK)D7YW1EfCAYsC#0=|Y5ezVG`#1|7w6fHBUBj`~w zkpV|Ir6Qu*T!4kUt-d4#ry)$0THP!zj1*8)Cz&8>^94t;N!1&m+EpQctD0N`pt}NL zwx^Ir)`EE(Fo@t320>0lmX{MzX^~R@e^Nm|jgv}?Q5T`kKQF?_LoI45E}OCX66C8& zgm@lR-L__XI&FdWa6?vCII5t^lGHM-jju673#%Y+4Zbu=f3X2{b;A})w4 zEk%FcQV`-V_&|jfNqHRp&6R;mQ1p^{)Y4c!Ykuu68MX!7L?=q(PejYMJFpv5joy*(YmXZ>tEoYYXE=E&UW4NPm_Fq+n` zP+;1GgHsy~1mN_*jQ+xe2MwfPsSwnxwVn|B^ja*v!y_{SoWeAJ4xL?|KD?9-S-UVr zBN&Uqw#+scHhNWvmG8+|gnydi5}>BdV)z(NV7>W9aYyE*BI9w677OE1MQ}WD56y)p z<^F&K-C$}aiVn2FDK=N(CA3ATeI*7Z+PVB{#iSQ`PJv*Ar@IQ0CVcR`ZXj87SE?Nq zf-zom)^~rzJ6mvl)@$?R;YV4SGGpCX8VA3QeOfv_ha;;}7KafXFFw!mtRLxJmqI|2 zq-y1m8pzRv4@4irE#ob|`faNYp8D2H+6%=%Xwu+@_0k}+Bh`6Jf zi}NgX_NMQK??xx|{8pG#gPP|s*XTExQ?;DRAA>|l8%@J>!4h15(xX*troswDq3wbP zCNjK<8V&y&tWJiL7K~mMNnv%1F`%od)21aLC!4lQ&y1l#q?$Q2Bt(N~NLu9EShBc8 z#vi8Yzv{4TW9wxY0S{^e`(>z*?E^wOHx#vtknVjBp|gc_XR>6rQkRk1GV4k(vI*g; z^@K9_1!iv73>{Q0n<2{$E!Z~eGR3;-6joL&U_W4gl0_NnkY>J1N=L}+uwB5`@>3j+|a1+S1~2Vyz^5fN&k<9s&f+CejkYEZs9C(DE_+ zQs%MzVR%JfcL{i&k#u~XSU(2$`%71Z<~}scE!xP0r#110dK2xym_jGuzHqR?;}O@X zeqaE3JfZc(y1}H8xVyj5J8uMk_viUUd||)eha*3f*ng_n2pMSQM#8vh8CiM5H2wX>Zn9Hi!Iti!ok$DHA{BGkOEG&#&9CP4 zMAa7(xrX7%7;hx->ge)MEs{KNKr4IY?KS_&xKck5TJB^fDYl_y|3{{-LGcj)ntg&bQ zHi-jtued@-E|x=%(twJW;>qo!+3dNW&pcTu22VyW!$(gL9G8s zI0~QMH8&6Oemz=QAGihF+5)s4I^$p_=xv{86wSk|k|gIua>gJZqL6VhU+)}Gz>&+E zMxQ?9w~q(2{vnKjXpvg_>giK0q%pfF(Kj4yHPo0kbVBx?j#N85#k+gfpHM7KPO#Y*W1Q5~NCb+m!XV%u05lftDwRoX{!-5~Mh>itKoRcm3#-sBVX86aluR3wS#9?MMy#eXO`%({jC$O0bZl3$ z6o#p4x&jyZn6mcBM0h1>>UuG7XWm`U!L;A5fC(P->pvR!Sb{n!UdM(Mc~XFJxBp^q zZ;(FA$4Tjvd$yz2K*b z=q9JJGeU9Avvy*nTG;^WL3EX!nvjM2q<6ZvgIJgRLVOo!g@dbm_0I0T(_mU33@$7v zgs)7%#A;AvnH58L>iE3O+U9Z6lKjtBTMWMv^iiz7r=(Ob$$^mkG8V#Us3~lj=@n(Y zA0WK=!-eRY0Wc*~-dUC(E{M1AwhqlOIH92Jh0A{L&wqJ#a`f_;5yus4%I-}@m$mgC7} zh}W#-N5+eIup-YFARsGaK=f#-4`!ATS_wtFeZ~Y-SSv%`k@A2RHNu1xvM@7niN`y? zIyoVOeR+X^*C)in)@maiP@>Y{I&R#44x zwEHWaj`D0lMT;>Qa9*LgZ&eqB5&?6#?89S0K|&7 zk;$UBuN{P(PKIK-R@f;EbBZxZtC3l0UWp4FLpV;C;=#zZwz^3*Dpw?z1(g~2un3u` zWd2D^h#PFZC$_^sO^blkC6F7ztqGx81FwgaYSe)$0@vc53OQlafRLW|64_mUG{PPF zz4`ZgAq>+(dGUetBw8z6{`ovioo*8Y&%xB%&^_7Je-!QPm)maz(Fz!)>`?UU z4Aa;Vyb5J^T5N;jiweE7CAcmB3S<30umWRu4yZ`%MTN}TeosT}omibfcbGq&5O44lp z@R3&GjX|u!pIjx}U<@5|Ug(igME_L6{rxoM7@fo=zF7QDNy^5;jN*{tBuYD_eThgb z1Ccbm!a5}ur5Zv=>%u&CHXpuSl5sPnCMO$wWK(=@t*AMtFjN8v72kus{> z?Y}s9_I$8=)E}H2y~r(3E-SS_O<;JTJ>&ye3h@l#NIro3H;92ilawSRA2czN@ONxx z;7U(bBms|m{6aP@9KAl6vo}3i)r$vjCv@yCzt1LP$Y1%5YoH6M*+;M(Hit1#=Y!<4 zukGSRV^ri47kH!tJzisyuUe8`2-3K%9}iGDf)K&(otzx}c@J&&!1mvds7*39dUU{EK)z*V zd%OF`?l+0i(G}w@vCcP8f{d!Zt`}xN#t)g_&+$t{C-*)U(ByjGmSw*&1EN z#1Lwrh;GchWJ80fjsYFO92!`z@26@OHJx1R5(Cf*dg<(@vxOs1!A{rGx|TG;9PmC2J4-pRN99t<$sl_-YxgITxU@$*8s8J2^fjeRcsW>!Q2yczAQ5gJI6$ z6KY2%vu1pWqOvNpM7Rd#RZ_q+A2QgyKIuI@czMc1)ZX9KJs1hv5PROcZxkYk`-g*> zoI`cK!DR64(k)C~Y#?UhFHeSNzg)w%EIqS5T5P>KFd{tKLJVdfMsp1k3478Da(B=* z+W9IRN?8tZ3na?{fSp)TrmX4%Vr7UnV%ulrgarqAR9$)sKFS?}XQdW$RVQmP4Hx1F6I&ps6527=Qv(G3o_G_@i4eL zfL&jdfk6;8Q4O4IE-zGLp{N!mF^-t2AKz?me-%JNEh^NZ;tD!J!T4t=7yxRwmPzGR z$a?MC!x{{1D=w&R=z)vRXYa@7qq!nQ%6CFF&-Vh77(8FO!^6kz1a7MP?C9v(i@m7b zrad}3W-`r4J5DZ;!#&xg65PjQXkZaDVGh=zhwBCO@JI+z%pk*HGaSV_cfe7l@+ZR) z_hdLaA6$+W3)+J}+KyC)U8*XmZ9T%U$CJP!2JWcFatoA09URoCYfM9VI@%HK3G?(56S{&#s4a$Z(tv>5Mm)KfM}_NJ&~#Sa`uxIAP3ZH>4h{ zY}#{-k`_w98ilAhG0tc3^rR86XSMny7Za|*6W`~^EWdbmECm0 zuNM@^N7pt~8f1^kG?NW7-h#fe6F!YNy?%q|U?wBPbTeW}xTG`3-=?v*Z?o2jp>7P> zW}=|`)}%xk+NrZ-D9VuYFimKI4Uv6E8;WSPil6zcNnaNft@rhd+Q6AOro4} zEH^Ci*e87QLSMrFOb-f7H|s6%(sjLsg0Y8cZ<|gc_Nyv+gbN0S7-SZUsSTTZ4KhZ1 zCRb^w#WUBIYTtnY}F?=(csDdrbcNY)&VK)Wy--6Zctp`%S zls}lt_S?}VZXvZFXp5!(rL58P;j<_02hw^ee`xj}b=iA~;1R6TFA@UMYDfMc{){GT zlO6pVGZ^{~|LYTJFi538F_{O~;eTV|ns;vv0G8eK4?!R1gg$6GgrGK@6PXf`pEAxB zbHQSe`y)uWQY6!zYTjr7~?p z!~wUiV@S}nF|d&8*}j8~4#NqZdl%1PH-SEa2rkoiq?xl3hNJJ%JuLr;+i!I6!0U^4 zjUCs_7M#FkKQCV3E~^YD_b$=t)6qi2i!pq{&sXgC7gywjdvV1fFw!!VIwT9qa6((p z()B=DYw({YWn3XRuJH(;OYQ z6G$~sm|qjdB0pU$9!+5NpE}~{zk~q?dvvjFONk0pfe;dFCebQy{8%my!0|irllf`< zj1s-OpuJ+KqHE$i7zd%%99}JwHiAD`UW=CN?Qo@5YTna!2I_~$M_(nhdvpH$kV<@wyA2jh3Zy088jo@591VP=i{@` zs(gOR(VPx?gM(cf6S)sHcfUi90tCo0LI{jMc5XdNbaUG2;mAf2%cnQ=y(wwtV$C7g zjO-QBNA%Qycy11(Agd%c(~YggBI_E%>Cqha_2#Cmz>0>j{=AKZt*c^a9l+44sGIZQ z*gV)@;fP44SSmrH_3J-uZ>(|w3zh!jzC=1YH{*`1c+qtRIO!@0ym|K>q~@Ci^^U=7 z?qW$fL#2!6Y(ym)Pe(ks zYpAtNt>|XFxd81{n{&07i|ge%PV}40?aqe_>_wCF@S`b@hqI36PqA%nu1~qYp*k7$ zwQ+m5N{$3N53vVXA!laOHzN=|;Ss)iTF2JQfL+$)jFL=K{JDdJyffZ9(t#>T&8OFM z2yvF4SO`)n4ROGZKoznC%&m7gI0m`tu?au#0g@5Ru{d#~V#A65 z^R>Fg=q$L88jyuRp(ybVH3UinrmRlN-N8EZ#oEAw;=1&xWWr2Bo9mfP>sk(N3>xuX zuAI5j5>a_T0X>-EB+(^H|005lU6rTYcVKX$9gwWHRkwm`NJqdpAwNj|&tx+2NU8KN zI_GJi;K!H*6~|cYLsx^!4WuvBz{ls+>|B*oPW46 z3zLmee5j>^0Vdg(HH%zhS&}){c3eS697(LPy`@ z3d#A+^5gAVQ3s;f3K`^P*K%)6T--K*MZJw;D*_9=q=_A~bNdFZ2rNgj6*ZXNt`&7* zimi;rZ|QcesMk|$Mfc>lYegNUVk;Bzqqtow>RT0Cp*g~+Zr2LU5k?hTVap+I58rBz z)N?De!d!e&b^;N9**8MOFD0gsHsdt*EZh1Lb#!Wws_4ZpMG=?eVd9s9YQ0Wzh&W=n zyNMs(!%z~xbW!OV?}sWvj;c0v)R(JE`hza4{gb^t>#09GJG*umT2bw2clYJ-!QRtQ z70(yE->mA%-s#>6{Nb3@F{EE|$6q3n%i{e%Yep(MF3+65I+sbsA9^Q!`04Gm#1WrV zGQ|pBrG~x~J*sep_Uz)bmSBlJvU%YA3oyXxhpw5Qw?3oSdvUK2VS(_#E#b6aNGXQk z;=O2T$&`S76Gv8=({OR7UZTU6Ob%fWEugXSX!86w9*lex&{=8ilRxtwVR9moCjTJHlwu!g^-)z2CjP>G(S!lf z5F}y|WeWXNa5usj3JJVLpke&dm4si)S~-nZ5`O7A!Y^G%_@(QJ%ketGudt37TraRi zlhLU%2+FcA>s!bz7}=_~uWKlciA1++3rxgkgs>^!2%(wLL6XELWlZ{kPonjWt5Gba zn+T|SFR1-oEXg1Tu~J4adU#!t-7@aMX#8Epm1eqKr>2yz^@o2IJ6mFUui#ia06o5< z!w8JEXvt**dBL0xBq*)N2O2_`L^$I@`;ga(?StCE?@H><)(ED7T%3~fFb?+qXeR=pqAAW9n)jJ*{D2~ zn`lEjK|`LI8|2Q*>6)!yZGpPYUu{8M#;Rl279l3JD!+CMSlbfs^j0+`+cZLK!xlof zjJ);j@b2;Ha5>Z__rLD^N@?emR)$m|11PB0C=7Rwu) zohw&1o*bMEp6|4-sRljLPM48pva}+k)E%|SmTKGdZ_p~+hE}#32C;=^MGGfTA{wk~ zLI&kvv(y{UlSmG4X7lOA2-g%Mdf*eMkDp~}@r<<;VXk%(GGLK$Rjf1u2=4vEv5q<| zqHJ-km<$=Hq zc&95)!N%wwS2qGI9g13W)AAGH?O@E4+JhdV(amuwaG$Ly1>)umap7|XGf%J*P8IHk6gjR~g_Jc1`Po5*>92Z)R<|r{BV(Q=OQFbOpu5cwy$ED{)8?b0x)G>f_KuFo&ZL^e#1mG97A$6oG_~ zounS=wKyn7905M}XRSqgkDkYWqW@9z47S~{mNsLlszZple^`05FzO(feT3SNWy2M| zrEvj-jsZC~f&hsW*nIJq#+;h7}Va>_@>Rx&a4Bsb%`;V^_&azZwB ze1;ie=xvqlVE-=%J1wPIK?T2LPGUkZOQHl9TgG(5Oj4rD2{>QqNvV0qpFi3@N9V|` z1)J@-@_qve`h&;1$u=@1V=~cRpt=Iga)KQ(Jr>mtlU+xFHae_;nu&uS{EDg5^F1NS zRqz=l67p%o@({rZHeMq}7vhngh!;nsj;YnJlQtX$&Spic9TyagKrg3w#+~j4;?|>D zgLokHPsPXud#L!zjM%#b>mN*5NMHx_K8L#JTVoPxZp}^BZ&{eC_n0<@vj=`8W zh-HKJ>SFVo9Aa#8-8rfL(>!VHdF0VV0@vE)#NX4g^xCaRjSs5GaJ@>F8rYA(ar-^WBU4aXvkbEa_DhLRv ziKke!m{X7ZF;iv(42MopL$Yq(TrVYV`*e2w?jrOqj%OsVwO8X~)92vFFmXFOyRt*-P%Nmq>URB&2i^)i`H^^D~-T~iLCl!U9u+7`wIV$iNv}n91BvWpV zgu(`c%8%$d$*mNj0;1sSMOVF@dBsYz=3HZ#MNEbAYkr+Gv~M2$arDf2@PKD5l0;d5 z`>wQ{SC4*H3LTVh-_aUN$cfam!DH8V%A(%|mZIX~dWyp=+*RTm=ud9;vCKNd;*AYL zsG?U6wNLqeQogZol6b62n zp!=l^Pmy4So2)}+S7EO2VI8u&@8IY*>rm~i1s>w$7iPMA`m2jnt^qvbw-S2?Bwaoi zm*T;ZU9-}GTQaS(C<`?8$J+z+V?986kEI5nAL{}7(EwV<+Oi2$#-0&%(ft-}vm|VKsLe@dWg{%pcg{5Tkpe6dJdwD+!_~=9rA%))JtGTJE1#WgQHY% zG4~D(GNbZ9SBaqB@n^(rz{InCF`7l4f(MlIk#egKy&P|9Q2F9>uFG75e}!j`-GsJV z2%53H>9%DHp#+4Bf^`RU6{L;jI70df@p%NPnF9vNMirzra+JdAsAL|F%ivwCq(P`Q zxglgyDI&>MNYy**&twm8797u@VZKBW+6bv%eK~ZWp<; z(wZ3?B9qpXc+@9rcQSrEDmAZ5Xr^g>fyAKaP>Jo@BHN)1ffb4$=|-;SC4+&7ePvnotY%ZBJREYsIIP8S65fBNumMfq`-xj zHzf1*?o)aqTDY=f9N@KfPmw(SVjMT(Pc%)zKC0pbgeB%Q4#^-hz?$%l@$HKIIhHZF zMG?nZp~owcH=E+Z2kA*?Iv}*EcP0wa*n}kP%48evw6HPh9~+rqx$1-GJ|ej{9a%9# zM7||bYYiEm_~mD!)H@Qb>S|RwkT(=6N*#5vnawr<2uh)~@kpb5lObLAZU8IcHRz-7 zqhgX!UE~xq&oh}T$PfCP${XPAe_$L+EkgV{eY(I)sZpEUg))xhX zNJ+e~vdL(%qfct?%TRhfeWh3e65%)UG4Q8hGscpkNHVriHhZU)IHPPFdO}8=r4+Og znYEouy>xDbpFm#RXt4Q8F(UKWKzrHlxsFt?LABqXWb*RmL@kjh__!p9F1u_?@e`(PqOUI+F{aT|e9Lai?uu|uj=154zVP_^ykaO!k+MIvhGml@wS zN|9__NtpRvLoATJr5s7xsaaWPJpNhi`%o9YjMZ8U=?27XNExdnnNlU3!(Pa)4q_l^ zA3%ojG1{9+Pf>ue2o4qFUqgbJb_EN>;Arh_%f`Kjh;c#MLXveFa-G(e+}d@R@9n3Ey+!*ht4+tc zLS^!SE(ZRHDmSs&g2jN)V_YLD<@OeRK7b0)_GBSnDj`2Wx{Pat$O`lw)@84Tt>d~A zp&Vla3MdXkk%rC4oui|Jqs@>Ff;i^MH^vDur$oKQOXQ>M`1oz(=QI209uuOJ~L=Kg93qcdcpJ3|BU* zx=eAtPy?la`lbV;!F)CVgg9k0PnzPTdM#N0van*a;Izj$?2mp^{>r&YAp+MZG~r_a z3o8I=K?|;6TUT<25;q$PQn`rj0-Q#QHOQ;U2BU*@+J1IR`%c5{}o!rZ;%xr+l(W(_?=1bK%=Cj2{K%QM-etY5%^lR&yoRPO)C zVLR@Bfnj^@pW?6`q-VDxM_p(8^7tc$U5_hj7pi9FYaY*yh*Di}KX!3tWsKUZVG-@X z3oFffB7s3V}-*Dl2ZEc&yX*9 zA8MH#6GeCSp=AYmdFzRJleeBuA4Wyk;g^Z?*yS4vK{{yC_c%cE9%szojHfCNZQ7Ms zhp5zM&1CwM%DsJDZ@F`%*R-*(XytUI*5+`AbAL`erlyJb8iBaCFyc?lDc84b{|<_l zIlZfCClM3RP67>0yQoks89e#Cc%#%R>AE-iDOMS^)6VLkx$tfP%^Q6XXbisNTqG)b z0fNwDAHMcFM*&iWxI|QAP2OtMFzGq;`ALeRFf+%LpQu@8PGC<(fCKx7}guccGTmdn^c?nl~(<%gLT) zjJ+Ont>HRDgKxbWxF-DqPR{3WWb5jtX-v0BNN5bW@L)H8N6dB$ApPFkd??x-2-6E z*j%Kw&;$I__-*u-@}(FVm{W(paV9nAkxr&;2B-<=EW!TDr;ae=W(Rk}Xdi;EwK$ z1`w!Ui%B2^A>38z54S9nly0zOdq7Oa5o0vKjNC${S0;f{_BRy}+1?D=u81g)rX_+% zws1I@e^?ui)=_Nzv1Q5Ptv~47R#55wkeVEdyZH<}joIw&?+jj@NOY7K95OE*Q6}bs zv+ow!--Cl zl#l}QT6aze*j-IyBj|iGzp_1vn%1ol5skx79DCXUxGo|&*(X9Eshw?Vi)^Fy$YDem{I*@ zY}o6C;6sx&N@iV6#dI)GQbCK>@!jXA{&N-T+M+RV3Ag=+Nu2m>9{Y#@kc;a|xDivk zc`A>1!}Vg>)V>uV1mzxRU|cT7DI!^r`?ZEhnUedVg-TuJZW9tMMfWO5jH8tNvNebd z1Jxw7MfuT;NSTEuG&WDueGp{9bF0DAG++a%dbl>ND^0|=I|sYFt4xGr>F)xS95!7A zsz@HSOcrRPf(bBpCMs-AwMw7H*CuspC{hbqnKJW%@$S;d>dy5Ygqm(_aJBt_$?qUG zUfDQ4YmJt7^wiga<)Ku_k)V|CAwlfW;z|=>7KLqJ;+&S7IRahREEcmVtW*i`JMco$ z>^WF!IuMqqYkma@Zgxi<{Py{tfNxLcuxeWU>+AV!bo!D9uQA!j%uZdsk*ztcV znz4hQ8l-n%`D69j|3oaCZqLvRJgi_q+Lb-4Xl!5@F&ex8)`gkk;*3Qay8twmF2WZ-2a4 zj4sB**{*mQWmB#ch}k^U=$c9aS4ee^9yrWDUSmA;n6C0JYB+pLL%iH{Dt%6v2TDHmBP@ z;jMdDFBUhi+u?0zt<(BnYn?TOqgPEY9jE+nE}p{EH)FWiWDlIf)Xb2MuGqir8Lr%3 z_setas#mlq?;;w+d#)suB%my1e{@=J>>sDv=vihu%&#i*4Z0gQ=}9xhvRNZl%^pU% zAW$^~hGAhSM+p!s=cgBhH-Stfhz;)O$gj5BPOfiegUpBei*rJWIF)=(ZS8h-YwD$P zzAI2(JGqOEWJ}>#J@w|2ZJKw?_q=s_GoOP)d?HlgQEJI#1bR*=a)DbXgGjXG%or2X zFs1@>@zjIWRRCEL3R#tN1oXx;9o9DK;nT8}BM#B@jdm!5iPQcQGiMc<+x7rv7Z)#_&v0f|iCFg)bl;|avi~&YMw`;8g*s7R$E`4- z*axe*NpGWL#|&IQ;`+C{G#kc}D>oTYPzpaxe(dP6O@O&S!&NeJck2LKRC=ror|0Y) zio#L7Zjv=cpQuE>C~8B#D>2xgrn?6JWQ_aVs1AcVy_2D0_wPo`6JD^yYS z8Fx~qpNr~xt%Wrv(vagy4CEmfO{|3Kc9ZVlGA?PWA(g%0rN&wm>54s0A(Ak8u#=Uh zFDIi#$Pn%#CNb01W}LH!u%t}!qeB-l6Yl*+(j}RVwvuhNeP1q}^bs=&|FY`=3NhQw z|HN`^peEiwFOpVUzA+KmKQEFVZXFDUM+Yx!?oLpdp|{FTeiauLCOTPjb=LzBsvtmz&J9a(SRC8;#>8tI8EJq@TA>{mW-Vdua zCdqwx55p2MAl9U$`eR#Oh)=HJztq^mc**@46LyMvQ9wU^myZ7P^_l`ZY^IKiS{ zM4U8h-&boEmMnchOx>>SS?#1fkS0&(t=68oZ>=$_nl$!CBhFT9%(mT-mPIcPRtT+$ zq|r-+-eMH1HENqo2BB3=8Y3A1?$sLenZCA&c*mhMku+*oKCjlOZAvgQA#c}O(RNjN z-?gmx>esQXIMR?UqmECel)>$)kP!=4*ot_7L8g0ciBIiN!ev(p({&%Q5xg8EGSa%A z$wt8!EH)j3j_`Cgo1Z1H#VGqW6XIDgkGeOs9>eRcf>fpxBaPKJzeds!mMp+1>wT^M zgu(uBwm*g~yl8*5u3J2Y@1x-}^(~lAaECJ-iKlO)`D+{ue+qJOX;tY%ikT70Z{{-- z-><=JceF5+kuCCHb8fy-$8|Qg7y53bte4mVmxOa-rJTz-1=<&OZdJa!*QZ+I=Wd-uwZCiO)kgq?@+9f<@M%n zl<2l_{TX9%;HMqqZm|pE%Vi-}0*`#Pk**1>rw#$-xnH0{8?0C)sI{L^oY?O+!69qW zmby@7wylxy?Kt)2cCuV|xy`dTcy{n=fTPy3l+tOb!rcHX;U&Vda1WLo4W|S2Vhp5s znlAcovHwCG!jJu%)(~4pqYIo4z{L{4QnyRj=xU%~I|P6f*|NEQSnmRF!;JiVn@Cx-`_H1GT>DAHz6`YT&{h*lVMTqWef^#E%L9hdEbdcG-B;>2)_u6}f4Ia?c40F?o&lQ6 z^JW9EEO6JNh^3@)E;gn`9v5A*zTDZi9W#=n$RqgEMhjllh@B|h5xw>8Z`M{4FNys7 z<+xj7aGU|-7PDChir;(3N~Gqew|xh5i_7r=Oi-mP-RK-i#I6bbk2W`(dYY?AqvX(< zPENKGGJ@9!wGHLGti;QhVaOMvTT8UHW|?Sk zDTWvgR7livQt(7`f9z`LjrI_(1>Bn7OcuM!j2%=E?YICXI_W1Cfin z__A?HC?gY9Sy&Oq`o6q5yBd8kJ(w*8HNBNA6j>y-Q0fq3q9fCd`Fcl~N#Eln+h3O*SLn%@#I>uQRwhyT;=Rdr{%JqEG+7Bty!|qxu6^D2a}wkxLDc!giwv& zNG!@?iM!XW&%0TDmV?R ztku^M?rpkH_cTLzcQ{v9d9ce3E?fUuM_yq8g=LK0qw*0Dlr&O-rhdM)j95EP+dqf3 z<8A+9){Y6(j~1kL;)=G_C%1N@;0brUXaW$iLN*)Cj$f0R^dJ++`02le2?U=QPxjkZ z!Y8zZ%ts1$cHl7RLQ_`Bng37DcW!FIjMx=wJWgGE%{Pi?2N-p2qq$PmOMxcKVO9-s zsur(B4)kj%;aZ|Sz(87@ACk1LVaeeNB2oDj5wnIupRr&7W$#Tyi4fay-_}6tjHSg$eBymFvKJJNJm31?Pc_DG!7x0w|(&(fHAigfEKvH=z zM)CAuH=wgO$klua|1CIOG^ta~r_!WKUP_X%w3w_#Y=s);!@?uV1&4*n9rbI|VGHX--Cya;TvfX&9;Aa^Qj-q;(cKWXrJNn) z{)C#iLh1fI+!OA>mdTdrBBZ`KSI>Jxn6aM}&> z#?pu%88dif1(!(FUfCr{e0$i(!W!Z-9v0Lb9MalJ?17 zq*@#>KDsTl{92C51WYqbR)$vQ`~F+0F3TUTs{cFbXOjYq!}(;)uN35_(@I-yb^IsG zNZXZVV3F+7iv9XAR$|afNeU>9&bZrf<-)mWIiBCl!M~D77B2lnINaBrPYd#aWNqiE z)e)BtkW&kpU`ZoZl&$fl{urvwacVHn7uJ6~4g;qS!dmMF^QyE?5c5-D-{9u^{$ zfVixv*x=clu7{L~#P-Ruh{|1Fc~DtASs6&3VTC8M;NHqzroIzmw|J)E3LGokY+(@; z)*#Z;FA@wOX8*5QsNi%1Q>@`IsnD?L`Cm$?n5S$$W}eO#E^mfo6}jjvDAL!d)eQA> z>q{fJkLIL=UrIM<0V*~;$D*%(a9gcS@n)U1jY0RAzeM3wZwmI!J< zt-7!Y=B+hi;M}i~6<0^p_t8g|89SJZ0KebODxAuH5M+qGGCx9ZYnN_3bPng>96>Vl z2?lIF)Gi7`;etPb%0tPMSH0ebuEP>Q3+(4>uucn`e#k7#ElG7w;NYKx`6Hwca3g`q z@_#Q>+)(y(D?N_qH@Vs97&Dph5HJ=FN7^G4in=%(I^>Qc{!`;5@dekoIk{5&2t&s7 ztw<}!7&jAn=rDeh6qCPB%jVNcF6GtLT$z@Aq1WWEG@_Rqe$+qORN4-jr9OyHCMs12 z1ujlxM3Zm5+JmBuR$8+On-utzl8OH@XYu?BTah1OS>4~o@ar|1ns612B?+2;|7I-0 ziCp=9giI$eD8y8L&G_*snJsR-Mh%FFxIY4EvmCq{nj9QY-*qQAlA7G{tFdWmKpKwO zO&3=a1X%hpT#mKo5N^fdD~ap;g9TLZ$~C=n!PQVpE^qZ4gQJbAqVN}4X_?t!3(!wy zOQ}SKp@Z1Cj-`&LWepi3j*E*)<|+baa5V@5t@gHgUMA&X-bLXU<`sW; z+XWJdsx+{wkHpLA(DJ%^p^3{6BX7UBNA3y9&up1;$oHc?IZ-pBV2dI{0FMUAH9Lyn z(Yw*)l2p?zoso%nS`9D10)GrbrQq)PM-UPq@SG5gj~KoOMPfrp9Bq+^@_O-_!eNED zudo+{Uosk)OD1h>V!(h#5NggY(25J%tH3LAOQp<{!;!L4bkQ~1&ui2`XA?azWx?Wz zhs29I@X)<7fU;5}GYC)pBD%^B=6+%&rRcs>?}(&LO2ug1x@g}hy9+lY*#rlXpHFy& z;5Vv0hE$cyQ}YTbg=j4P!_aT=G!lumm?Tnw@RF6vsbc+*0H&!nr4?5wxaClGr#Za%?r*(hrgeOE z%2tw>vPtUI(WRs&z^Y?0o-;>2JsI5n}hq-;L%-o$A&twu`ZEHDCO1#6I1;PDNQne~9?9 zO%x+(Gk@d)%LngWh4oSp{;ANf@fLRzH}O3k7YQv?=z+V4^wUJ)R1=|Cw#K~)A>!sf zF*wD_Y$ilP4^1W!;UQ}NO3t9S7&#A9XLhBIsX(4Z;wvjV*|=X$rZ+fNtNV%R#(ks` zJ2y=>N*VM{D#f@#dvXDIV1+tvD#IeqZnfc9sTwqsj(73P-MFiQHA*lZ_fGMou;EGt zT;VE~Wi@nyI0cc|++1NfOOG8#^NX67dg0uz1l&B28K5;u0S9ujH~qgi}94d4a0Zk_*6X2=Gxt!EW%J! z8HclTgW4Qa3y?hNoM?4wrE8#hk&#Ti7FA32taLI+nI;-#@Qn(bdb1(lB>E)1RIf1q z0vqF}3=p4pH?CuQI8dbbrNXf|dsiUt$>T@XzqWI#=!y%HfJh4x*V0Y-A6^Z<9UOc= zc$7yf}Oj`1Vy2%Uyx4Td(&kO9Y*b|MWv@cU5ZzIWl z`l*l8oe^bclH+Umv|bL>ik#Ds#sY~O&Y9JT#41if$N~0j2vZq;?=G6!ZwY=SG5+Kd zn<9Z0n7&3%@q!fYoG4GmL*q%{wRk^1_g)S5hMTlepWP0a{5BSXB;kXyQ6|Y`q;e;+ zuv{brmghVxSpeK1jZCO0&yV$@fyngAGhKVXhLVh4`=(l`C%7 zWld|J;XHyh(X=^dVItgdQH-uwhubF}aNVs!rVd5E*y_TDADHPo_}Wh=%iI0K<8a5W zdQq}5h4SK22_jZg4J!~hxJP0hdsmM3rcE=C_|gg3 z9xX@C7||WZx0rE}7FzC@7-hU^tY#}OV{n&u{ z)EHb0_QLhsneW6IQI=%geR%%IbL@hMx{!jATsKA5jaf$TC->5}T;pQbU+E>@NnL&z z&bDUo-qMYX>}t$J)`hr6aD02s##i(dC1Ny#`tf6N22s<+Ce7qf4G_}fs}8tR{IeEB zsw=c$dYkmN_bX~)_al%MtfqVC6L&zb7w7R;HVs5}Yj%2ib3K9A4%bCw8qJ5K+N^#i zoU;9I9fk8KT7S=urgfZpZL6(bAhH@4DbQd#OS%dz>svp{F<#Jx);k~3TL5_lzYkxe zB4{|qMN1IP#^Gw9kj(r8nB0GMVdpQ$NPsb!E*HJ`*Ji?teHiE`6z1!Me2C$m4VC7H zY9Yg{%NO*!qpCPDMB@&tl~(xKz7yF=h?pZ!Mk=#jszj$cdx=gbUZDYob%8TvOr!zf z!q{L`1=76|M0w)ap$W&hb)`MxVlB4W$!N;`ITr4)hWmBL^Eu#%M&<<$sD|+)#8VPV zIqb}};6$ee+}%500Efgt42Y>FBwwPXu)CyRAQGik+ou`*lDO>a76bT(GZHH3#v!U> z1I)G^*2Z!;Npqw@~Qln8brIicD7 zLk~5}Q(chjqP)t$k(Bb*l&Te|KSQfYew@HXg|hNB0Zg8u;8@9MoTRA9KGT$-y15}) zUlcbo)e$SR`5FYk^41fQ)g%2_0Q~GsukQG>uX!O=Xx#`TS;y)6OW|~K;9pyq-L-n% z!b;m*Pm8_jp=L8~+g~?R2EBEeT4*!rD9)}G)GS1G=%XE1M373DL2vN+MxbH?c_1(9 zNP1Va@4*yn>Ct7#+p4n8O|TeDvQ?jguu5dn!;hO2MwfAmsKT79*a9=q$yZI#mtD_8 zh>?ztsk1%ClE`tc;E9_T+{j|PvpFrm(>D+e6n?hKxOpuUw-6r~P_2yxt5_dvR%K#& zYKhH~<%zbSxNgUDmg?eKy5(evbg*o>Efh&ZemcX4g}IvpD0rtpVQux7znVX9GR2nN zvK_wJuvH;OS*F86mvbtG}kB^Rp z*z66>u$$hkx=xg-n-U?jUg%sU&Q9DYPA~ zWHEjSH*6_I{#^7gq_HyngWigFPGuZ$Gnd$)j@Uac89{f-mk>ev%a0mxP@BiN028-~ zjSmnwqzqi}`?H5auYw~42`0>!cg@5jw7&w)r^K#i9icHgSLKh4@0U28ot+E4n4D!> zC$tduwqe6z)p~(!+}+VBSFv3z0E`)*tJ8mSCNOTE5EWB?Q~BT7HbBYiUn!!Z6r~AJ zBYTPsr6xR^-<12-*8Viri$@EOZdfc)vPspQ9G8L%XwbXbN?3)jiH(dOOipk~BXi5| zx`}0 zP#wJf^Z0amj1QsI2ga84z9dO_K9Z#Y7Gf!^fDJN%v@UHJhFw z;#f9GMFZ(DH=fGyWO|#njBGMn7c!e%Vtj^s{d_7vL+Yi=@$>?BXR5=pg)<4-fw(iT zbc-Mw#v z;#uf$a}RXx1g90kR(_!%EO8jjkYWet?X}~>{`Z45r4N#Wtd~;)SM7YQY@uBV?{U8< zj;S4gnnV-Cr+b^8mDUli?Bfwv5(Xey4+%wse5qr6unYN4>hwj=Y8l&1rpzVk z{v{@Y`y-y)TDHq?Lgrdb$7-u3Eh41EF|4*?rQw7{-7%7VA6!#oF51UTiQp@}&bQ<7 z_4MZIH7>aDcVcaWhgmu(imVoOX!fF~KD|hm5Y$O8=J6BDKya3S4KW1rEke|bKiQ+Y z4W&#KsJ*I}jwaS>;zBOxy#9bY&gZglc8PUr3uB+RV(IdrgqFK;0Q&4*RNt_KKau(qj`_7HnIL|PdXFs=`VWtuiND} zoRe+o^@vW4rdcJ}#WIw}D|2n}d9et^zHSv$Xi#OqJbWCkds_(Mq0 zAC$th*X^xizV5LS*Q1GxHq^5+X*FHwP3fMpubZf>{Y%G=O>NvG5!zN*!0~2j%d(ir zz}Rr)gUyD8@UDbypegt6>NV8bjQ9-07B>5kF-3oG?2Vg$HZ3e?n~oe{5Ei+>SZzz6 z8%8hmk}vaH`S$(^wg~hduUr!5PQpYIT79_Nr5a5^;g~CQ;cX{4J1KV^eQZI9Y@WQD zHWoC!+a&Ot>H|nx$Od1Ru;SPN_BO>RE4}Xd8$@8PZ%WDGtbcjwkTXyBhAcb(ve8-Z ztp63x7W~5Rzjk1}cAh2~m#e#$Ow?7K^F!!fa+QxRsYy1s=cD(cUgAn8h=}ZtpSy4g zfP8obJZSRkWMi^a?(w72tDum}?Z!dg)iO(#+7?u}7q@BV`)6l4aX*9W`sO;UT1yo}+oLJ^cjMEiYHON-z-DkY&C^hw50;aiKV2t`+pKC40%@}L$8JY| z{(CI5RR+pImeD0Ax%bC|!OqE1e|zuMF`a(xHT&4&*R>A&=zO)lZZ0}RVqS9hPRFgw z#u8H6N&DV=XXC35$n~qv?|=XMAb5g)O~OHYe|fY8$Kis4tGAYXz>QZP21-ikJUWBz z=GQXlHn1EcE4Z z`3CCkO;%#Qd>x-p=h!HRPAuvqz@k2Wfw!kkQVn>D4zEXJYkpN_$}@sK-b5^SKB*-VQ=ktd2@#II~?72d;Q@7VDG=1jF#9@ z>pXf6vGA%t1iGKkM`t%!pX24;;COiSYDDdJpgtC?JnH+?j_*xYN+F1k2vSh|Te#Kj&Q9@}nlzVPOr+Zi@u zS`;g3jOy$nv0Ev%de8dDJMcDOnkU$9!J7^mErM?@XTX9z%`9*xp>x=5t^bESG;Ab^ ztkgYPu-x|ed~|cUJUZCnDwUcgeK{<@+&O%CbgWyTlZ8B_^_K9oVFr?FyO3-zXYYq1 z*&sy9pCAwlFSkt0)m8HEu_m@vO-8*s-Z|QR*?%tTrhu2v7ALzXMyJ7e`!83JlhRTexpQ14FJ1hxj)`n&U@>uaV!v#sNuhCI;tQT zQ_Z00b3~_H{EjnI18-8Gvvwra#z5JcTq3~k>N`=ype%o&Xn$E#IDXf%W8l-O$52ZhFHo8rJ-mTQfF;vdVzC)!_gdTFVv97`oV_JowaRT=p=PfE3{V+zlHCZ z7pnm_4#Ntgx5*UyBCC~lLEVfYYkH_zc2{fJln7*XBl4|j&an_q(?nTWG3!<>d~mZo zIPYIwL31|35pnFl>Zc_l&3`qOl^jRtJs_mMa451;X}zceEw4aH#zQFVzv`g~b74Q~ zOU9Y-Usm=7rt@;V^A0+_z4IMZ{Bif$1le6xyBtjAi@d3?poDz9WM;VQtYY@1z5yfG ztSa9foS)ZM;+V42F;tBrNj-3{nrYX!wPCNtLHE4 zt8L9ki+nERO??GjBEO=8CLLdVU0;=zUe0(8FbxYsjK8d}_AM;LtO{S$7gxLTC9cC5 zm!K?svqo^Da^k0I)Si(y-;ZuJB-1R7u0)K03VA)kaBqL-NbLmPo5{Ed3;t`Fp5w%)EId zg=8NAn8748Qqtm5y62QQtACJR2v`r1cIUa?!Ism z$w%Y;|LhEhCv=7!pX}|P?Cc*7|Il$Tk}ZHc$;L-xKAq~JxWk7|@}FTWfrdD!PVD4< z^Kt}>;1;2JX2`VVd3f-#AyOjTyWoIA`=UZR-U-xL*sVZFslD#mU*UmO=}Y&e{kVJP zHx#D{r_{oo&7G**6voy0Cos0PL?5a67c^i(p-F(wI@-+7OR`Iy43d^GV#8(^a3wMF z>MpS>Ya}Iwg}N{zJ?pMJil_gv!&M%kZN}Po$w;J+tle`*uVB+`?22xn6#j<1)^* z&#*6%GJE_KA~IH$PwFG+@2?0&X?r9ABmMS3IG*1Rj$}`!1c!dRHyk$i>dw2*yomp= z6ffhmre7Zfjl1WX$??>@!BsT5v3@U_^bS`f2@cVFucOZN`@N9>xxg4(F)Ym^;kGbj zNAc7B?Ci8{^9@(EZJ+MZ_TIvLX>Ie|`9{jbZW`QMgtNQw&w}x-oT>js&z^bai0kj3 zZ*Fw$gRa24mia7IdJ_%tsa!?vEzED%1XpzJJsd9EDq9^ck>ObAGy&#i))?R`mJ89C z_y8XcuWtpe4zPC%g3&$wbZY%0=GH`U^Q<^wH&BZe`6z0!yB$^^Bs;}P(>uU|NBU!E zm{9F(uMU)XT$`YZPT7wjiKC;v@9qPa?Ko8ugzrYJNDomMm1!$(Vy#44ai6LZu=x3& zwAx=*wr6B2TUsl@rzF|u4Id%ecD$L&Nxfj&b!H;qR{wr}(>HO>F^-+|BLPLhGB3e@L5H)gz`fwre30h>1Wt zfyEdf&am3n&U7u3p9V|A_0846J){&=b(ZTq8oBTgm(ok{!qFf>!jX65zcC(3X+=w) zNFO*U?IlV}l}(Ac<57~wkF2yFeintDx*8Ds0_ds-4PhiaqF(bd#K_IHoj_lls5Jom$=DQPYr0Ee7`O?6wNgjjkaQs4kiMLsP)gQl* z&vyJm=QBx+kj^9~L$I6NS}*>E63Pp~9?~4WIhWA$tzPdNG?V84ddn1KNNgx;(egt@E(Vo6FpN&i}X-pfHPx9!3VrBOyojH)<`x6G%D6LZ7 z;(CG}66L(yU(X>I6)r3&b+ZX5ry%p)o{C zQ#cX&W_TaS~kVN1&EUO+oLP&+66G|YL@_{=)2i;>t@dU*(es7 z=zEROw3Dq)$3qL;jQd1q@=Aw*Qsoo&n#s#6elBE|R!XTH4H$^hf&K2@2vTs`fSY*U zRolLD_d$+fKJNAUuXYim`Mk^L?!ghZacF4VRq$wM>)^Ycqd#aU<|;L|`iF8N(gB>! z5pc>NWd%;>oT1RYv~LL*VyQ46Z_X~tCH&-|(+&#Z919|ZO{hmCb*0~zV~aY(2Ms)b zadxqdgR5=S>^O*!@E6DtqsL$$yS$BU2cHDzLkA!U?6pNejo_u_(`^m#_C~|F9adH` zVEOZ#tRO`Ba&)9*JN#1Z!Kw&JA$F=nY2K#~dESvvA4Y)Y;f4)>XGmgiTMapAsgUN(Uu-cNNe^ zLJMOu8ADhTc9RD5Q+MY-ceX$?gXaZ4O3ElQBCN@$?(wjHG~DqVa_GiIqF_moU;~c( zH$(Bk3aJGARs?pn{?rkE6x)QN)>+6F-mXYTYWR0~O}L<6Fm+a9^2~ zjHH(-+^|>Zs>=0FGB;g>U1Muv{Egobsa!T?%c_wi(%<@}g3|&6DWF+d4T^<*nvx)} zwCl(ZIZmuUzduY`M4S?C1O|t*bZmEOoNV4m={yvbZ#Xj$pqS61ZkH(N+5E|dfiX{V z7aScR7&`g({(5rufeKat>e_vsNcg6Mdw+d1ISZf3XU>SP_O^Mhdk@a15ADzjy?*PV zE}9+3kfBe`E+8`Ccr>XOd#+ z{k2b}9DA{IL;Hn9q3WpgxpSxS|~rqD330EiIg=+K-(_0M8rFXN$@ z+Dia+&WedY?M<}&wH^8QbNkqRKjzVvE%qP+L8+SCvxZLFsEzpdty|7s2rVk$51Se` zfBeNw+F0M;G}R}UY5)9{*Y`JB{lOP~#}X zQt}0*rLIE@EE6=&<@I{@VLf~?Dr;Mu>~j|{?p05 zB#?Gg@3*Z!(iV{+_)qC5)1RiKbxG1<$dj~s#0fL9AYDr!`5*Y=3;y$C2l-ua*pBI3 z=IURS->>Ojm0x1u!3#@1X!rZ_uK!&B9E0*7ciaxb1Bibj2^2dDLWfhr6~EA|{~wL16Qt0@JS?{>B)-`?9c}VJFQT+Bg|LFpDytugTF6J^Y&ho=Dch$^nPbKnQTox6Z&!;Jwz{ zs}_(aKNsTbG({w-;BmDY2YNX^M<^NuloS)#WvY#=up0RSFs^sbmgQ9N?+}u06lAdL z+DsO%Ist6?V45;7GP`C2MEs=+BKXW{6i z`axi`Fz-TDIyb~4jx}q-lZ_5ixv|YK>1apjwQ)w764%W5hC?3=P z>gG6)?D0v6HGJ#hu#|LDe*MZtZn-cQcjXGw4nNsFX+k};!c)Q$>7KR7ehj5jqnCY^ zM(s1}vc0?n>$6uYWO6Bs188FeG+X)L4f0ZV>e0{GorL!W8C8Ur@u9 zGF#i!1+8g$n)^!A!RB%Il!!5adZ-&`$Jnz9R6qw!poE0LX^gr%D_XcVSPyzNK)r$J zc_}NPqA|&A1i5MjUCEVSnQ|TJ{pvZ5%TgQ(!aqqbNGT=PP{Q_e4_#|dqOH!bNB&uK%ALvogn!`kV{67QR{ht?tl)T-g{Yv}plTqB-1syg&kb5vKH! zBlj;8nt0C@2Hdunat2ymd{V741H-S53|6&r#4dp9K~i|gDO{f_KDM>QV8QZ)g$A&y zIw1gQDEE7SXI1f1$|YLYs@TV0Xi4#RA~z0Qu}AG42reKprMn9J$vo|-x_Jm4&ZZ+C zh_@Dwd0GkP0Qa_a3OmDD$3EJsrHPpo4%|2>&A=o9A?Q251&YR}y}6crbEJ#7u;yjz z6U&*)rLG^L=*GgzTrzG8$g@?wwHvam6%Hav0{5M5T}m4WL_SiqK}W``bkswnjyvt; z#9F2*70=#!cVoi`25>0&m5v$t5fpH$Ssk_w9Nh*ABtj(0rxWkNsayGea7aRB-j(`AZd|YvhOc=TB3q@f**lde+}x#j~x79Vrnd5Rl`R&Q^b#aeA_gJ>XxNB zg#?f*Nn~|_Q})7?1dp<^Zmz8+Fm?vd_Xax$xRQRw(Regg&0thS_^l&*+u;PIlPERL zJ;-il7I0_OXNxYfEzjT5Uu%q@R_^;2E-QjV!fAyz4ld)F!xulg+}jTHUE)k^2qxzt z?~h_Tgn17POUAZp{p2f-)yZb5N%OdL)&;aJdT^h4h6;Nva>0JbrF;@#1Vp9!%}|}N z>KzCSa_wG{WZ3_9=U{iYF#I6O`%!DHr&?<@4h^+yh+4&PlQ+z40^*Izv&tyAKjHXk z*p(^A{2Y~|C}6H>BhJXeyeuWcc{ttN9|d|SHNtLs0{LKGAXrZos)`&c%{#1;tQnTS zpF+Mr^nE?M8f^*(9~N-&$h^Yh1uSKka24JeKMHRWjIO8fQol#av&8Brh_F@WgsS276LS%AR)W~eNqykiFKHd?_NSQqsK~z zdTzPIwN|A_F=*Al#=S(6#p7;W8~oa-X=p{`gDEPjr$3mOd9o%!w@;YQWYAT4Ste38 z3?Q2ZTYx8=v}>A?%0=tlG_IMIUn zAP(dw_=~JP*%AT6Z2k#hMI36Hu)985k1moHPl>enKb#y`g!+0lK~t!o#8PM^zQ*$Yu1G6FXWaKam*(mX$nEzKo&&z&vx_Y6%oKB0{q zrpW?Z)lE2hffL7)ExxG@O();j%=mJ0ia`DG9PL!2$DA-tt(d4-$gt4L8K*su>ujp- zr!k~{UOgP5XsRzaHAvq&2=d(ii=U_}6W+z82$NYCl=ELk=OD@ba1$&I5hZpl$1jiL zO-w>4`sIMZUiHMwz;uU&7)59 zi4Ohve|2+W>cv7lw$cP&dal2=!L-mF?Exh?><>e=cDw-497QO#QfqW5)nPBTrf#Rk z!76a@Kdk_qc&S!s?dZKr+_FT9z`1Lxu|byzn}}rj*wYC0yq3h(`^9>QbnmpTW;hEb zztmSP;qQ}d-zc*7*c{N_Lc;a_N!qJ;pLoI(yQ?}lT^P;m;KTgbI@XCa87-Dmn3`{F zzbl?*wl7d0!8Y8~tgJj}CtvN%6`sxN^6%!U4&R5__v8{1E0NtqA_r~vrtc<;$?HoK zlU6Rg&Dz_8P@ChVRtA*6%u4S1>9DM5cxz=(bQrkNr$4~z&aFcc$adpO87GwY*-{Xe zvtQ35-vnccT1cKL{#70ap)amS@7al^uJbLsOj1WxDBT^%Wt@^|T05yOAogpzmW>0b zZoj@^`{FHPCCkB5@XM<<60@(6>RHnC3YI+Au_Xw$X}V`~$Q&(=#WdCTY6nRNPYwnr z&v1tNKu>(%t9n%;O0WD)x6qPTfP;Mo+RNDLWS=gHB zta9t`z5;|Z;&IkNrKijv89HtVx3E?_6_#-V{H5nYH{72^@q0Jou)p#!77Zv%geU0i zaB|8B1YU_8L?2|w%NdqO;F%}nG1#|;F}eWYX2uSaBx20#yqaF7wN|hkJxKXsBl5sw z$eij44Ff93^82z(wi|`6f^8U;yQaev6{Ao1NKn}7LAFT&gUYY zipuewOj3iPW1Y2_S7w=<0@Q6uiKWGrPXNzKyq1iMGq?tkWB^BF0)RA_MMj8Mk2W*y zXAVR!WioNUS=fBdi3Yw6GCeAV+db7H@(`RWFec8@>ej>dcznGzyS~+B^Kz^oO|7t{ z#2aXSk)ajpvKjLyh7>e~Dk|~{89*fnE3|Txq*Rsp!vIpBp;_{>(RGMxKbO5w6^D+O zV;oV&%f$~HKR$|OaKG*JEw`1#L`dGhf&2Y>bhal~L8ldfXxv_PHMTE-tYqaOlTya3 zfrj~KDFij45&oh$l!MqxeDZ2?uXw4T0@RB&4d2S?j`1Kgb4^^J%j(<0Kdi{wDII!UWx7myTI zQIYh=lt-94tJj~8M>a8xaK+_#POtK)&e7h#$U|qN^JD{OERyna27mFxa#Z~2|M7`Yuk&*2Iyka zdm@e6dyOH{n}p`$E1WZzlZcDXB&uw3BCkwZaOb*>WE5qImCHUs0_H|4{@eR6GVJ#-AiX8Jc62EjAEiC3)cy(yI3hBd?3Z###C`SlJ=b6Y zC#VW+Sc>@&U|1lJ^U#shjNv9R#PEm}b@o+y*a-MPDuFio-ffA)mVS)>OI$gC{gvEAlfl(k3VD;bN`5jv zOB&=+9eQElyOsgy3AnuZz3D5E1pMm3zRH$Scl59~cs1NPI;ifXOsiF`i2-xUG{2-- zFP(pO1o;OPutB6(_q zu4tt^MQ{J*&e8BIHJp5i6sMej|* z=bP{?)UTL2>%o1d^hSW?-d{)0*PSHN@~#M2%>77dS>2&xcrfk3i)c3_gnmEY;7rp_ zAIR6i1BX3e_iTRf^~MUMC7>^nInw~~)WIeJm`hgB4xz;Sn$K=wlNIZTP-Gj5I}C#l z-Lq5ZZTy824RnD(qf^8?u_-uWhero5WBOX@CySk3gO_4P)4g>gIigLfaBG$=Y$6z) zURmBA8zst0eV20QKA`ih(KX^X#}DY8DX9l3p{3!^cMh8AV?4X*J%_C*Kk9GoAd&Xg zw+{wE@!mRubE1XS-s#(W!tsI`xgXs73ue!`XOBlaFAuh0EpHvXI5-+UxND^No1J3G z*4mOQd5b`d7f2|iNyI1{l@#Mk+$~7DJ>lB&1oy`z+UQ;skTs&J)U-HzlpQJl5PiWP z-LnV-L<24*<~czJb*-$z3Q>yR_m2kHuiR{9%3U>a5Q6N;ga|@~zFGf`@MzkRgtyr0 zJSiQ68u(Ioxk~CB;#$;(@@b+2>OfV~Cl?$^rvS0l29S^KT${|*fBdEl9sC7l-9(LL z(prRJ^s1E0mL*F8G~f5A+5IBWHfk@DpA4ZI)q+bL_$Se z8X(CVQ^5>JqKLMJA`PBo8^!j$GgSlAE>Eyl#cNb!bBgt?L#Z`6>onk?HvoNV44&)n z#iN0yD};I99$0{0_~_>A`|I9c{%TAhk*Vqi(bWNYQRYb=b=d|!#wF(8?!S;6Tu4}8 zmAz%IrmGgqy2?h}KGc%9QVR8SFnV`{SyUJ;BDfcpTpY@LpI%)smLC0%( zWx1^Az%uFl<*%K!Ej6|vJ+mus-(O31S(srKe$~VJgr0?BDVa;}lYR!JQ6TgE^$$;f z3?$tZN$@ZY>vBJs`fU|)yra+1n`F_uiKTFhc>wINCGmBUz&-LSS5)}hU# zf`G=(_lK>2`RwcoL>7|`cUlfG1p>Rb>ux1rY})x3;L&H)#loA{owL`%`1tDgu}C51 zQ6}*g`5Fl_l3KuLZV^L^ggIzQT1g&2m+Kv5wa2Vwm}2Qg^Q?u}3~-`ExBr?X$-&L? z;M@a6`~5q&64s;>t~9X%rDM3?Z+UfE-bX2F9EooI)Q-1jZjzP&V3HppQ*e%uDwMfh?b@+! zw~HH~kbUBJ=g_MTwcbTmmp4J~B)GX8AK(H9y&}ybPxLOU0)m7qjn6(B{Qsx|HpK8~ z0FP>V>1uC*qtYYfi9^oG8h-WyzcFVGF9jo_fEe&iX}d@+WvIMeVF)wsp%*bs!{$+S z)4r9Lw13Fu{+W{3iR8%FH^TKu>#L!cQ+)~YA(`1zujdP?EFrAFJHggMdhJCg5RYC zc2zX%KwADXx!R-W7peG6nzd%+0Mdn@Cy2~+X@g|sc&}3{N&5p-TFJFp{*Cj6(@FEm zcpl1AR9i?L3{J$0z-=%%KEw*TtDm~tJM`xsAuWnX6~Jn8L4jxm6fs4*a9(H}S3a+& zn##59UAkA*3QPY4=@d)zDfrcBBJl;ajBBLT2w$hFC38i~i$KHFG3@j43n1|PhQ6Ob zL|+I2eX$JF-))h4DUVwhxDsG04qzi_O}{PoU1GuM&^c4HP&>SIKe;1E$B>c)cSZ$B zp-_U!H|a^_Ue9Z%Z$lk!<7=jYTGjAraMHpp$Z3o#p)%7o$QYWC7GptcO<3EWX*AMv zTw?dNPXr$OhD8_pJyH7(xpd^N3!>FbJb-~Ia1yMct4|BoA2YE2$QU^Vi&TC;%p8bk zX7PpT4dmWJPWUc~d>b?JjPwKw=kGs~rHB?1WdeZEs07-$UxtitzVy54sUV+=)5lCh zVnfhL14tZBKFT@+mK&r^>%VTsH)CZq@R7=$D3hI^PEK-Y9pVxhtpFA1_42ay26^{` zJRqR0_P;=-B%s<00T%aqoz04hBoWt%hbC|^mcS8+7vQYGaVK(_e5HVs|ebwZfV zCXik*3A zr!Y;`z(AVbv<3LlR7!+2OfL!)Lpki1mf~5Q2Tdw z`P}~Du`UK<$**B$lLmbFmCCCN1Y*|>0Lb!EThHHN`}{kvv=~ z64dvYVZs80abm$kQzo7yX-G%P2};XGnN5 z(UPmmqDX=uI=9)`&Rul3p>&7AN6Ok*ua>tuihF}}EY`yX0K=ReZ@t*reudBr*?B^0 z;KZg`5mv+IY9PbyYpX3me1iizpfH2?`~Dve5B3JwI^|ONVc^2cyb5|{?)^B1kg&W- zua#bT3@V8rWoQJFHz;F9NZNupg!9R1>+EJOmp_(K?&5ik)C#zB@k4?)qIY@;a>Pm| zJg+MWFX(1Rm6^jfO&M4-b&H-bO<{2Rp7RsSdw8poOvJ6|4f@QLq)VM^VcrO zettXiLH*{wr(DXq#Oyu!(#(%kZ>th`6fS-i^5v|BysH&cSOXJBE!@sYy!KYs#>|JVb7PJO`Rn_>N z)Y$kp)DRqG2QJm`Aqj2@3mD@=Szdvxk&teNUP!@M*p6H z#8^21ak$=r0SwPyyf07$J7Cu+p3e1p_ZEn}-ndt@8!I+T=_gwh{2@JubDN$o<^0q* z>im*dt6N@)dnd8j8$3IBHNag2I!$y3EG8H6YS1lvp~JRPRUFI2Q~Yl-eJ#7yTpnG5 zL^00@f;iPPleqd@+ecj1>85PpnS1(ha*4yBY*;)A4o_20^V!9C&KM>W3rG-oiIjJA zQ;m5@7dQ3btOl2A^zmPhmUxOcO3z0rZJND9J7x1!_&K4*{Ep+o*r#vbb0+|q_LPhiT0(xf3wm1u<=`w zmB>m7d4*!(+tc2Mr#0W^a$P_wCK)pLZmhZ5s073oQ~7azIl3UtkX71YTzo_CSMMnf zb*X6a>kc zI`!F>e00=wUoFP-UEqmak7JoE;Nwo4CG15AO+LY?*bbxUI1Fj!Z!bslV&xiC$<4Lg z$Kg0~Oli0-!ZHz#`1!_-gJ#orE8svu+vibZB?O)NZTCiOlGTSAI`AtKGG!OZ4Tpa}`ZdLy7l8WJpS z6#yVjtBh_m8vx{eB&GZu{|ZA&@%t%Z1swoZkj+-aCH1cV5-vwoxYm~ySw7|9mMc6h8_6IOG=}s6?uO|?yH?0c>dGLWeuiAV0 zzP~rD+w@a+3>;MH_5fTyK8onCcAVz1Yj*%n{Gy`!NkVCBK6BIys=Z{V7Rzk1=wZRD zO3#BO?Z5vdintN+M^iNwJ{*ouyn^pfmXjn~LHajkp-29xf_0P`v_g+!kf?*8$~k~7 zTJW5>6O_TK%>Cy$zHse6fcMyZLDU>9Bk=@SkK6{+Ey@M`7>LG33hcL6U%b%(IqVGc zq~jS9x;Vs2<1r}Hr^ogKD%Sp)ft1^h6ow7fnxH%OoXM+TwS`4F8#`XkbX(K*(E0A3 z=2{q#^DLV{>cOH8tTopNgUbLJAdX5FE7zE7PzV!VTuC~WjxqsA5L$t-0R!D=E)4{sK42v^%%aeoyl4uh!5 zYAGb4n~6OiijaL1^J*Ubg2ys)q@$^d6RMnInscAbz(wwKjC>Ec{!(-LL(|FFQdc+I zwbzG?C%2s~*dEG(#DRiXM8Ce(g;Xr-Bv35v@7wvJ$4NsQV}`ff%yR7aWSX^<%$K+4 zNApy&$P5V2>nhp?aT>g60#-@pMRfP#PZD9QsLly7s+3`q)$muvSfLk)&k@9AtN8cu zP9+Wg%*M4Kq|pEGli{VNliPToa#d_mcIZuo%h$j|7(ZovhGlK z+CcFxP$$?s8RguhQEM*OQwYs9Ge+UGO?w3xZB>%9wTTd4RlCw~%frHH`b;w8s~Rzn zyg+)tlrZv9e$Nx(f+ed=?%^~BteO;pTVaymcc+tM-o1dQxR9OqPDCR15&L2<4qhT% z)N@#O@0E4aTV-m*jSnbuRYqg!BU98FL}70n9xl&2t8&U6eo+{h7jk8%=E$Y_)!HIh0&@9%1>R>sk^t%scI`TX_0HO;C6Eqn)gz0 z=(8(FO|S!?I>@S(gnm}q*V$=DtPv!+X_D;;#)Z+EF`Ov@E7G4{T#kEKM)1Ikwfp(+ zeQ(E&V)u6H#?aFaT*BNNvNmR?-T;P?s2=aWBc0?+dT%+C`C_TnI}T^3)s91K=(#*) z3tX-h75eJ-XWA0wT187tw(|G#5Sk_`LlLaWXcZFN=xj1A0Y7nS8LGk}6W*n`qmt6>`j&W4$j+auLgU=H9F@jza9_!!@aFFjjr|P zNZcf9SwUgbkfZ1?xxw`cN$236B@9MB#(0&D%Nf*_+t%K!ae9uc{?px#rcRG{h9^4a zgxB4wV2TEAFr9Qxn6r*SgxBEvg^h`Wrg#7G<2;+76gHL^iuiaeZsu^toB*$xLTxQ6fF?_Kzc(s4r#joxGPIBx{=HWcM?R7sEh6vfJtLW4fkdaFGR zXUyB~#N}4-h9w*4U59N|hC5DJOqDZ)k47|M`r7#Tt zzC;2>NB$vDi`VmsE0+z z4X4K0Ks_%*xlFOm04X%9g%aj%pu=M@6r-HHdw1fU5-W1h9V@#;BPA#&8H?W*)C>tYYxvF z)^62gS4CA`LrJTWu4GE@K~l(VCO|xlt}3zPO)_B&*goASd5-Ma7r*1+(_>mIW3L!g z(3XLAl)2&vFf}h+>YX7G7JrA90Hl7xD&@M)kX$n?4QzpCJ!t^s%MEqCYd021uNLSnV`_Np)|rbyJzb394%Ztnx3m|{ zZ#~e$vLAr^q^)N_CcbIBQJY#+fEW7a%WiU}5-{)2UQgi9k?ro_U5)SK2C`yG+R@F2 zmy;_G*eb=?$#vJb^_t;A8;GWZ5t8i#%7-ui-Apu1FsH4?l!Y1GzlEaKjCe`sW(x7r z++dOQ@qkOG9p~}B$KG-_m5Ue=+o%^!bkA`+@XdI7dTR}7VO|DVQ5d&^-T?hL7L}Dg zn8w*Ton2jF4UIgfC}lJhoUx%-uCjS!MAtuw=>&VL?JtlrW=&z$Os9(a(*>YXYd)5I zbI+`MjYaK|CMe;VGyBbU7YGQ9t?gXlzGWCaM*noGYnle0KH9QoFLVyX!ZP_H>0Bp1Vgg zEb?v?;i5>DfBXe82Wko*x})W3 z{K%(@0^Rv+X5pJ`f~DjYMdm(GuC5+B9#oPGOr5p<5MI2zE5wvtS~x&xiLdTx3A2A~ z`v;*KVN!Bd782G=Nh0nKE?+Tzk1gP(nzpmq|PkjqOe3a&k%!wv^L}NncGFW=aq^JxapCn@tn&^850hu z+odAX4=h#jR{-}X9TtclLn%%_AqTX1g1dprmx?3{uRyc9cu&-;stgO|VPo2*1^=cA z1limRyGN6;SiUnHv2Lf@yYDS^>Ov!`1n2P`#2N-f`vV4pC!E)xTvxom?v5nzSa)*# zsn?KFmeANSx;}4iLGz)Nd(^{?+xM!faoXwf+7?c@#U~7pmlxU{H(D~5A&lXp%BcA9 z3BH0|^r9h|6D3tE(PKi1{_)NxjNmiLN*}8Fde;}ZQkisQlT6Trtg7ai&6!i6uenFs zM~D)*!2tv9U)I$@O{a@+XFa4~Q0@Qb=E7s|9BTH>1ui_`_9~LJji?JU>t9}4?;x(| z4IjPkd9?tIWm)Mz>y2KMLX-S0q_2>M1qBkQt(G`^vLfN3yO^AfyMvuqNcr|sXAaFd z(1~*&0kYO{JOf2Qo^Ja#nEE0_aG!mB2J~oiXFdLYEH4;puVRlHlL61XtmGi&KVb3q%k9#$53XkL=-y+CXlPBLY+CnD@GI*Laqxf8%cl# zdg+FU>~y}xi5dldal3$pCKoWlcsFba>b+p}`NZM&dTDJb!H&>^9apDdk~cYpRgzRj z8?n-Tse=eRL+a-#1}Edm$OE*fpOlu2we=OLPxKx%SAmooc^}mfj^QckK1#$FQkbuQ zYKiae?*!MzC6K+DTqTWXqV(?~MoD^d?JjgmFiJjFJ$|fS1S~nVFg99DVGk-c0+e#(|_9`TO7@6Y-yr(4p6T z&bV?ZEKUlm%Axs~B>qzqZQ+eA+<11>alLG5b?my}=xi7D8$W0TTmBI$YiNG47C<?E=y!kN)0x-2Szg1(=qlJ$UBK|u4;mcDhz z!LZO)rbx-t>qC|+xu}+C^7WxQg$G<1L3;4Ynhq1~VdE#xa~Fx3aMH!QK>Qyw>f;$a z_8D?mV$TCRuKt-XadO<@Fm90vw?m7#>!!e)A{y+bGGUsK^j`B7+q-yhNfD4;ewAc1 zHqGl0t;FEIUk#&)!t;Sl?xTJHvw|eN_BT1U)IiiBR|!H{0>Pgh6|W7(9F3sf8WiW_(w1$OTj`EGJh^ReYC^1hoyN*v1FQ&4u zEt);ZC_cITCl~Tw_+N?(vqLdKDAcncXzUPI5RK=y7XUIdzna}7UNBAV?IIec_NI4Y zKd4{0*<_FKS7$3MFLh5ZlLD!RkZ}x--AA|s#}`KfXtIJCFxi)`KCutMmx-}PXT}LU zjXTI1j4)Q;O<1<=1sH~VQ1%q=Sm#^fh<_8&(qL>_3$!F-x;`WVho(Ril=j{SH>jlB z>X-Qh{Y@S-~uH zBOYw$fojmfnz-k3HkzLeGH{IRO-`J3gD+j3>NW~YWs0l zz8IaT0kiNmYV9^6aO8dU#M>-)FJ=4{q83=NJJZpepo2JdD{+}^XTL%#Z8qf?P>$sr zv-N^FW6?hLrGPG}5m!Vq>9JxCZ?jjddY%ezgjJB#sSdxMHPF{3deBIWbZrO_^r3sTTlI zZnnp&0ya~~ zdn!(OY`l_nF%Anr9^#BMC<5J!n)S2K8NHX!K|jd~%giV&PxMNLx=s(3(9>KQLC<&kr``R=hif-AEHUTBTlN z8{5yu?37@(skVaEvrQ(la;s2)O-4LzV2p*`^tpszfa^@7FYu1khcH4axL6Q}E)nxz zevOoAEjb++F|5!C;$hmiWethG-PN5b7*;cX~O2#Z85(U+g(1a_w%t+}jxpkNrDYJXkbZ znhK|*KjRw58j9iskpWN@%W-Tvm{o2*(S7&|*cDwB+jiIt@g8|9k75T*>f*C(OoTac z=1Bk@YeNFdAGoUY2ZC6IBo{x(dkzdyova>&uF;hiOHgVAiMZAA#SAlU&m3c4HPV)S z3b-}~J{v7ZX>lV*szYpK<0h3*VfGqihST%`|A^$VMkx9s@{^Y11SBPUX*HW!w8j4m zRy!v~&-ft}JVBpH{Y27B(24qd7j3eKvP>s%iv05C_*Pp4sw+)61t3CAc(Rz26G6QO znNUCkofFV>!uY*t#wu_UOvuJKBQSW(O&!*BC!Flis!(9zhVX#>&R-M+g`^|BNuM2Q zLt5&H0~yzLPnK&;HUt@+GMJK2@5HzSpc5I9qL}*KniAbu(@Lay?tT0l<-M}~9}q8}n8AXRoRz0@oQg&q+gt9P%+AkSZT9yV z$*!vgxVH^SDP}a})JexBPMkM?lgybID`{g}K~|F}k_CiU?uz(u2_Z&OFo=B7M{yha zN1!O4)Jg&>#k~eor-U^}xp(x)UUUUU{`2THHn-jeHIK#w!@0YSi{kQY1`>0tMWlbK z$(5B8li9&2w*d>=ChlmAM9S~ruuCw$X(uw!RjuPwnfc zQmvorYr#Qdn2~(*fUNzY^V8$bht6&1C&7|^Mgo8SzZr2ztD~ASSWiKB_L5+bZYbA1 z7{1s!GQjIs?TNfu`jy~VdEvHlUCZM}_|Rl~O)_lp>Vw7FV~V$`GKZ{<@7DF`{Rk`F zs)GIR`&-AM+&MFOA%425&Fc&8-yrq?yCh>AmX^MxsGil@ z%*Xi*nLx*Eih~sP!e0D!q0li9hV>h+=6mhMebD-%ax4|K++flG@~ZJ^bUfg(qBo;= zm5smZe-{gd`NZLrS!MAsoyf}OXJ=!55EwGh#4nXifH-)cNi4n1@A-=%ScmRa^sRHbdi7@q z`_E$SH1vO^@%IPcCbgX0(qwF<*Fb?b{oD#ztpdiU z$hZR%lVWi2h&Ao=vRWrfRjUIPNI>}L9`%I&RxdBCw$kvKor)7-`I`$2-5G>f<82XQAm?i;{@092%)Y!=eox)_71fN6oj5g2(^jCK6+HTV7!Cgi17XK z*}+_}ZZ^CzV5r)_fEt9hkF&*XxsOn{X|O+aD~nHg=aH;pb9!>K1LU*RDxK6cvA8uh z*kO6wz*5r+Y64Lu!SxR#A_keL{ih{#-78;_*L1YJS^TYcaCj_<*}6NrA9yV9yW2Z` zTv_~PJSFdoQI5qk5`K=q={?o=+I3Z;9~nE>x;#m}*iE(A&hc^o`Hr}+ zIl>NZa~jT`(~i-lf!GYAHvtK<&W^5u3Dua zdOG0`os@2NsDATkjEmLB$2YIZKYp}f(+(u?U4MBBIcAfa)%9r^^#0ATh1H@B$^A-Y z8dDZ2zxO_sMcK0ukFazOvn-UopK?Q~C*;wfH}G9hDBekL1DpEGH(huT+y4T*3?eb4 zb8&w9!{o=T9k6;bIF625BdPPZUi_X)fsAzWZKqw~G>@@L+Ja+JK3QbeeK6z8ciF=y zZZ>xk9`sz5iep>{J31r3+(>+Vi<8T&I-w2lYM{A9;yOGjbud8#>pM-ru)OATuOVdw z$!e`rtSRzCoFy`~;n7HJ@fNv&?rQm%g<3Chz4ql=$3b}v+2!dgPPPvA_xpqGQW}e6 z-a&~JVV%&A65xrVOd6IqnF>lic8`azwy{^aQg8itxOETW_2IqszuW5%4~|yq?KyVE zZlG{JqPyofHt?!H+*zqVW&xhxoPOK@_XfwqqgPw2juCCflU zi^0MhZ9(n%oT)p~*BymhGF^Aha52-Lo<4!4?H zErM5SFq&ctE7CZvz$3tfng^x!Om^Su=hFBoSgHj|8F}hIp&W3LqB1cTp$FHC=VE#! zSca(9qNy0JGRBT)^Z9KDr4WXDj`Mdn^D#Fn=$DzqS!~^omz0Vn!)HS;Go-sVnn2pK z=2(f6w0h7vwt^u6E2pf~+-MRDRW|EIxYPHk&1_=>KQwWjnz^;cZ@rlsW_S|=7u4~e zQjWzxO||dV0MoRo2|qotm4ZM>)r4zq$8$s#8j&&5+)7hOF@tRrOuM2xc_A*W_?iYu zpa^MLttD5(jM!EKJp{srG>56NAcprB{WY9sa%_ZhwHEe(6`BVYWLsfS396APOvT?F zO}Q|uZ|Umx9i<)GAwQieFX3lKS{|jCavFCHd0#RqQ{~t(-bjL)%DTa=g55n?Q89=_ zmRC)aY)#AF0tRvBD<|2qw7p;)A(Smll}4PExQt0{oYgoxS|hdxS-BT+D4Z8&s=$$C z(ijt>WsPwIYWb|=S9I~Y8|GD#f>nJm0Wq6m7qwSapeNB{T==aW4Ay{*gWcV=OsW-5 zDsovG`fVXUDJ0{2Ve+JTS*}Q@k8#1Uq|DC+VurR$Y-c&|HB;#|^IA z*$u)8Z*Z?OlDwbuI%(c5jg3a33idA~Ug+9dNw-V_1=~~xFtu#LGj0}x@RiPMM`%^G zl#fz)tT4ns%x>n{*f@wkP2FqSLV}gRaB*ZI^1#^6`>DYrsSD`JJEHon=%(9})TEqI zTu47hIvgIC;nVKMI^J9}ULc^j4vH(JL_!j-L4YAhNwl}~+wb%A+srI1yhv8E z&%LQw#G2Qho}TWWo}M0A@FCX{P(7)z^=Y^OubwL+*k@ON#_~y@^J`r2lkHV|T6p$z zs`P?VhDzR*pqe!nv;$*PDkEx4otnwBWbB3QU&{%)eMeYSa5=3myjd?McQsw~pCwnx zh$kCH=Yr$lDLW@Dfe8V=0v(yO5aw*A+ys-Ftp%3cm{6{OhG~IhBFgk=4CPrRrZw!| z(Czg9M-^AlZA4MbW@kPgXOowSaj~1pOr7~h^Vq(`1tA*4JJTd;z~2xN`_4;a7Bsp?6HWs zOuuH&8qj_~8wlIeJ+B5nL~)^QUT&KQU%`BH@jAR<)5}(QAB^>SGyHnDdsE#*=`Fn% zqM?Za1F??GmlcejVhR%smxIe(zZyeN8#f~l)|Od+bZ~UKe{j&Rf7{p@Lk}QNs_{~! zrfyanb`((GuQuKd2UpXHFeKcp-uNBsP>c0YXhzHTCPHlY?r-LMVl5$NDR)R0fy9og zL)qodsz+mZ9FE5W<%o(hF3(!xLGDlBT!))=Ou;?yA8_dXA?=yz9{1n&~vGpw?bU^U|e zHh#H*I^^q9WBhCF>$8HjB(2c~=vRuC9rm=91^wDX#n4?~V5iG)wf7e0!0L4Bblj#e zNpYU)V1xJ;|B(X=MuJB4)8<`2CZY|p*!KJ}i6oz$sSec^Uf%GO;Kg3I2{2fRKoQ4N z4|1;ZYIZr}8VZG)oh3Jaj{q&%5kHy~Cuc{x(tzS(YcAb+O`%TgGq|&LEmf2dDW&=a z8FVNUDV;}7S+zrk$iO`WY%Dd@g??M3mb*VW(V^@>@4o3vjjrrP%5r~wTHpT*mPI-?Fswe|=hcP5<#%MUxiZ&LB9N*ea4UEsju`ivrL_Of)iZeaW#Pu`54B zlfbVR+Z`R6#tDHUUcneQJ^w|1Szn@_Gu*>lw1NVEy&c~27mgM;eA<-=Au7aa;07I5 zK5=S|_Mrjm{u3znpKUlW&XMaY9H8#%$smFdze(qhiA(0hDTvv8E*!K&7P<{C3Ec+o zXIE!hMf9DxI4HD}+DgYdsc@ZDTxvkN*ZCg)+@HgCbj(|e_9%CSFU01zug!{}q{e&4 zeEjmsXz~>vN5m7c0y#mnjKO=!(8(vTEsLHzW63gRNfI^=p!GnL)LXUsj1OI3!9`aO z?X_ZXj6Z`Z`%ZiR@O1B>lvt8fiYs`Q@>r|Flk=m&On<%_jo}63ZV5Pitu`)(H;AkR z=>_1l5D%~*?0Fxk!qZavaRAF#kzl45O&i}LM(efKIM)wsnLwg{O>)lK0{eefd{FI!IEMdB46SQ!cAH3Gu>K!{&nnG?Tu3TeSiZ-)y4 zDC7D6;cmT284SZowQ<=0see#!ilDvUY^7t~| znA0lcdC{A!P+|c=vn?NBqK2Cl7HT@9xE{lT;Fy-S7aO-#*- zn#X&)?%|VHoD6TFX^vM^wiwREKF0SeMwcl}G!H~&DrofgB_z0v!9WqVVG)ZtR*m(K z!Fjt|aAKi+EApw7;Jrs&R-y_W?MN7E?j>C8L|-{qj6~`V*#ip>4JEFg_D0FAZM-cu zWI)e3a=<=9R=3xlAxgCki|r>?RS=QY5?>tQ?0r(DLMdIK39ye4>XMDN#^%Y6kX+2) z6cl$(p6>0O9`9`~8uuJJ>Ly8nFy=b9#(#YL{iYL5V*;D4kT4fDv$GHV#RFe$e}In>$2TkE%z2^8oB<-b4)GU{9C)tg@|Ei{Ek!HVA5nj(Hh zYq&LN;%r4h3cK}6vPqS9>ohrE5@>!jPE<7lDJY*EaW-1`X zDP>AuxEoV}gm&`Q({E%LR~bm}zp{5I1w{!LGk-feRG+aWyFcv@w31rrrk`znZKE)U zR}@<@9(8z1?G|-qtq$_xn1fVSii}$pg+?n}IiSWwq)AB#yq1Y_1QsPe3W2GGZu)5m zEcyxr=Ec@RV5+dH3q^QV>LUR+UN?veLgzeM+wSN-@eG?;g)8}kwzSJ_qO`<+=G+^wb>572 zk~&mG0cFK?kR_7;`|<5{7hB3VLtYfBZr)rEXZdev(AS7z0e{g6ESdsu)XT(vq+z@H zgEUn~qone$5hKDxB8Xj$te@nT1dkEom!Zb&f_0<| zGg&oAOq|&}Pp6QAv_VmB@^57sGGO~HBes0q>e&aJ!Qs$>;}|Qr&Txvpbz-|l42%2L za@Mzgd`#bo?!Zg&E?4*3p68OuGg8vD+VY6c2j3!46HIBV&>FM@l$^r;gWJxsTy(2j z>4$A_?MryN{pM!Ylb;{kFWI=-Waq8b`G-spSDw;+(UBFjZd*$V5LQlpx0aHY5 zt!23N)*|@TT0%GCT1I}SQY+Sh7=46BPc5pFnK;1Px@j9Mi!o~o=dx;7RYQv9_~A1d zTb!Y(fMh>PVoUp>eJUp#=T1tb1pwOG>_Y%hm}ReI@Q148YwtDX6U|Ms>RJY&Z77Fc z+Q{+-r~wV&7mgbN&Lrkwp}u2fRy2lXv2;{aepe65B=_CYrd*F*qq%f{Y7wIRDpK3< z(oy0^9eoPf@$}OyOyKEcs=DY~4m(J7czIpTI^BD6E9V;IIKAw`aVqA9#g;?ca`35# zYktx-cfctGZz!7CL51=hPfUjulD@5e^4uKpfgd|OJhvrjXOdnwvTeb5wyz&IX`0Y` zSQn?VvKwhw?-WNoT>e>fl$o9bdWUkvXNw(KIPZg%N&d>%7*N_#p%%Z3T>a6rN;Ozl zhdw6!GI;HA0q@K((@14U3ul_#X>QP83&)K9{Jv*)ENV77xLJDNb_R&}a0X^$Z|CLG z;h&X5h%=olo0|)mTd~=_W?0+)%bD8J?pc}Msp4mdr}wbA!=unGTf@Ik?r}b7RHX}b zrH{7?(ogX(c&vh%xB^-(A=D8OORv2YQIOC)KCb)V)AzqZt4jZ1q5e$~U z>f%&X9xrB^jdF9vtt&f?B1^{F@m2^wNUuZgCVvOCMB>+GWktkCE1qaXf_mPxH9HwHv3PI+>sb z&E;!}?^I%ve)~NUpj4@~CKrJ=L8&XhAoOuQ!m}a~_&ni8Ki32x^`j+$Kbz##x)iLn zyfP}3Dn`|nBBq7eNUL&y$`iDf>3D)(O>sppOHyqwOAwVW`-aw(H)_hCPcr{1DPGIf zQ%5iz#AJm?@WnLzVmxW=EMA0U98EydV%Vku(894f-^sIP!y8>Wazd9Qo)uHaGNMMs z@+ng3=#2kzZV8d>Q2$1YG*fj}8-R(1TgLe!N*vPC_$=Hx!Kru~O{8Ppdl}6zr2%@; z(#HWV{j>l)dexO%QUVBr+1O`}EgigGLmt6f09u7kxI{VSOzVz3AAD(>{v(4gTKd$3 z2ciYvpJL$Z&{Uc`?cg{F3Y^YN9(15IbBEFb%eZ_Am&s>HbmcPim0yO#t45eY?bWL{ z$fpE}3(siW%hz?IzAPo{rj=;KdqoSVNa7MJ%&85{M{L|+!m_p~aHDT(H5|Ke3_%oF zUm7zM$n_|X)Tc|xTT5tAa^=Vi9wcFT+3ySbzlh+{x$shtxu=ovxMjKu^DILcaVkwX z914wrn_%&_pJB{dbVC+5E|_zmB?KeVBq&8X2Z^AvWaOHHBygM-pa}>*#1~3CFj9N= za}<1`B{#Bh<(Z}2NoW)liiOFU;DuRP*30gs4HFVpLHe2vm8YYIC`d7BYW2K#ba_ICq6N5O7Gs%l6mu-PrzGV3QqH zBh$7OSh6&@+$uDRBgdiV%EXBBSfn(x4$lj{w?#R2zpkF|AmIWd1_`|$L+vg(S7UUZ zi|zLG945c{;y+mqoVCBnD(GuzUR?&j#=Z2Drza?!AwWw&jfyb*UKbH!N4(5G-!)#* zk{q<83luLGzd0Wlww=?TFN#?CJ9T|g_Lk}Phym(ZXv>$*=OwbQjxw#ts_CROvtvNswB_K z5+(3n%9yi4luTKvN40@TR1EL)$|fomDweUd6>B))^3`Z-S4!!^ohxDsmgBqTwnkYp zLDeCRW~?%~FaY-DIzRMDMr2e1v@Li+Yevxu_7r1>i_Id71*aIfbEqV_V4 z`4B-kj)#mtiRF=_(brfED_iFrQogD(XoK}cy^Q53lV{}u{h_Nug;~bvIDC@ zMiW${r-3Pk7+g~$M%(g|h=I1hbMzFk36*DPY1!qARZqQPd+zJ%E_KTrRnJ-S&*kNC zpJe}VX}{Xidc9_W(_~4_4MO~PbKy(qyzQJ<$WCq+iucKB|8#$+;4jF8ZDuKh(IOOJ zIaSHDTJ(CI@eg1zsma7+U`#Q)92_Y*?!<1yZ*FvM=|L>tXgU9$P|Hg#mmFoT^~_G5 zRVug(rX5!(gvg!4WX&f-wXwcnF{#jE9%BtJXD1@#1OQ_l+46LyNd=)4I-etyaN>3? zjpg+4L)5v|Hz76HFhu|G0yeWLuY?aeBBr=1l%V2nWTp=KrlO$@eyoP7IxQHR8E7AM ze=F7EhL((wV=8w~aoxX7734NWNSIVJXBBf5`mt<+lD$TW-Yj8)!5`7S#%Mb%}z13kkw3v5EQfelV0!(uaO0pDG*#zU^F44EO&bn$D7?00~ zY#h7%H#Rbo)EKvAeuBeZMTx9ZVK3}0AsN@*Bf1& zeaDM0{GUokMHtEQcBYT1T=BwIH%@+IJ0s{W+hfS{L(nqH_}MYONJgTRYpZ!bw=^s? zuj{g5DbHLP5r4EQ?bbfVEJ2~6s*0%v!mUuRHwr%JyBCg!lw@=_)Z$bO%%QPSfy#k7AhHUa4zn6` zF>EW(o>&^Lhc+$WHHw+HprSSJ9UEy*;}`sA>nmZ&0es<)GtdNn@|K$ruv@NyzYqVQwMsve((g{L^;M8d(7$3zYcf5|LvxPRqxE)38?? zGCMUQQhkME;Q!WH@^2i(=kC7tTP9MSfrW_v5I}Zn4#wMYri9)x`nZra$C*7+&H5N{ zqYx-+t~FhYE`2y#v!5w9USxJ!t2B9SIiVz|!h;LMWy~d+b6Ec`L|fnkLOgS3t5sCB z7$a^1HS*-;#}m~D6);_#ly}&=sScjU(~vfWiHBUo@w-^k+J>aL}DG=CPGo_!`>{ za|b$%J5XhG0bENJeKa-|g7CK2X%6?EyW4_Hn^jC^xaL`nv}C1b?r##^tuRCy*tsfQ zgmG9O?t>$^2cGLFKLT(F$8SopfXlmJKI)%9=7jcbkQDr1Q}j+hzdW zG9X#PPt>_LU;XX^V_E;>t}lY<)9zYVNwR%wZmmj2_9|>iu2wAC$49|Beo>UOIQ6Z( z`BjE`6puC@vH?Z646KOWAEoZGlzNGzA<1`@cbvur-HNPJDTSP{FucueEN$TmIWg1t z$M@=N$@)SazP^RS3%HF#H!EPh7p+>1bZdFpna6xDb0Uss+s12D%7ML`oU`=AFcVTR zU&)QiRxB5+*$S+tWn`>8G78ZIVX%Tv%^@=EK=GEMHJvunvY-v3?Bv?YCD%OAkfo)O z)@q{{!6#E>@L9p88DxRw>}-E#{gc!t=^&NH06hO_t$iwxt*H0d;J$PMQOK_}u>-rVk7>pg}5 zFiw7P3amr96<3<9D?gd2o5xyqt|U|*mH&MI3;h3UKgaRT7B&)RV{^?sz)@%Vw0VBCMedrm*%qc6PRGs5PExf4Vzak6$QMQNK zlmcAb=H$ZoDO^%3{k5qmCTsn<3a=y>l&sEG^GgCtx)ylawZQAejpTN};L(*%YK7|jLJ5cE5qjth@U zSx)uUdC{C`30XVZd=k*BS3{$@mNm$y2WUzi;?PQgS$8cat)^uh+LV#9b_->pK&>m@ zT2vPELWS4Rl$uD?m(Y<%tZSHJnna-;QD=FBs7HrK9Yee?>!oC(!04oYJo-Kw+H;*P z2bBhb7I0&^%8uIY0PD}0FD|a_HVtq&MkiB%Cqxxs*7d`V9)643Xr#~rd924Y+lFiN zPWz(-<4n%?h}vGxnZQVesTq*UAT2tUb1kja+-+#;3fUoTyJg%jj%m=>?xwIy%3tf; zL#6Y!$*Ws=-Q>!gF(8?PV;5YO>>bg})pm#a4p zEv-vRS^KFrDw)BaC7o-Kz$hVSdZP*MSJj!MIvl?1%G1YNQIMw_cD3{5_7dSey^k#8 z38Fiu3>v|*Pq1DOau{{P&C9}Glt8ut{^bK#wXAHL`K&}x&fW}&5IZ_2Z8Q$Fu3Cq> zCN4T+pEa7K$q*q7-ryx;#Hfq?pGZF~;)X*-M`AYGwuug4EirVxw>bfY% z1zZRZvZ3nc-kL}zY4UU|Q?X0qzGMqCcf=XNIjm}__Aj$kud!2Jt??*b6js5xc_Isk z50H0nB-nHVvgDW-FJ{g__LA6v2B=x_BOdxQ(SB@S^=Sy*E`vJzaU-F4wRxhP6EqBs8B~Eb zEe9dw?!P}C9-Z>aPa01A`qDSQ$~Jk*6Ylw6$Y8fNCiR4p?TS9Q7`gxWQhvn5V4^3r zUDa`_RQsEM6}}^e@vpkx+!qsUJvhV5SK|r6XwFl(K6_C5$^Y!E1U&vD7}ng}=-m!Q zv5)?PQ0ZNKg)kxBz+z_hQ+{L2c#U)KxAkvO-m5$O_iKIs_fNsU{}@@n9iA^HYP&b{ zw)eiqe_x_(@BPNx-W|T~KN;7ZE}HN;eKk&A2GC{;_0_h7YZJe= zZaD%u>h4ACxP5Uq9RK7s-|Lbc&0~SUF!iNn|^7Z3C}_TwPD0sSxE{Dh7k@*Xy=RVu~Q& zL6q)%MuHveFc~Z7zkTj40h>Wvif&->9WY|Y>&@M;^x@Jjh9}aQ*Jqm*V^0(n3BFW% zs4v(&!t+9>(9U|A=Md~C(FMjBx6gWTX-t# z+{&_A#AggMVJ}&-C(CG8v>BN-r{;!^$_X{t(qd~{e}a<)^!#GO$g?{K-kq;Skyk=h z{0Bo;^0AMae*?uCo3s2jMz`!}!A9hIli^NI-_sC?_jwX0_2URW;5 zwXKh&@wS{;I+yuavh@1@W^KqQrHxZ8V8{szL2iyaJreRYfYUV4QJ4{y?N6lt=2+e=`2@A9H?0}>L(I7 zgb$7<&tq>4N{NZUW;e@;Fxz%n#j2bD>}>79I_z)l7k0V!>l4j~GM2a8myuwANd;nF zM9!x)3_H@ud9J6@Hp9Ic3^|-NV~#ARb6(VN!RSR(vG=uXmcPMn`L33o@;AM^jbCf* z7131h*9LLq)S$!riNnLT^Zi#7Y&h@mFDidE`5ljW%9#f?WPVJQon5gOVpel&N$4As zoT{`Sd@k4-@RU_9zlnTrdou zrKa83yDdI(cYgb^{94C`U2<%j~^!b!{WQfi6Y#vG&ZB_O9I3^1DZ zMC%`UnW)Yj$}B~u#s#^XYjq)F$#_Omkc*VYTDQr2px(ds1=}h4Gxyy^BUoZ)^)+Mz&o=4j%GHwPA~hGk zzLe>e)Q+%=-a{zTO}UOlGOe_Ie`@c67KJl zXS}^9zfKQmK9_@9ZE)qWtVHJ%M8|giCY@^FJV~gK`w0Wlya`JUS&eb^lm79Kd&lxh zp_i5wD-H#fT%5t}(H(!XOubUWxCV@qd=z8X!&l_5DN2wxs9p8g*gqxcZyO|%Ex8Z_ z+K@6bP-DhPGzyo_$qETz-3O8kj5IvKlYDBctzf0%ncUh5!4QU#RE~?QF#xPpOKa-` z9-~s;WOybCN$+j+3%>XR9=d^pAVt8yxBf0rd_`ttQR7(JrpZ?tl@aIhO`UqjXwlZ% zvUQ5FS&M({EbrRkvcJmdl2+jPk}ig^Gkq+ecrZN6Sp5j=`2en{j90Dimcm#!yyi38 zU-MN|MG0d{#O(lFNa`>2ni(fyY7(KD1z;7?$}b3ceOi@5qeU>LIVHFda+~9SED8>>P4bK z^m7e6SC+4IYNVO8!Jf&_TK{lv^%oS#=0hhYIc37Fe0FE^R3AzaUa?3LoJ-wg`90g; zoqq%M9`zT$?U!u2DJd=|1zj|}!Ogji`Z#*Pd}X-$87VK4VkjE0>8OGE@o+G?y$Vbl znswaZbA|_s?MB+^VS6ktt#K+MLMJDrh0YJ0xMO^2k}|M~Gt3P;xEHtyM0}@B>w+v# z#0A2iql#=7cv15OLO1$J(!w${^W~#&YbQO#@Llv0fMsE`<}PyNvt*s5*~X-;>NriJ zzz=$t=;b1LBqcDKos_lDw^$Ws%T`(@=Kcp)nnH2fveGmsk< zWXi+P>x#^^V5R*FrEj z+o}$Rui@x;D6t!q*ocjmO$Tnb@BMJ}>_lIjUf4)jAlQtGIJrxMd-LT%z~1rk(eXAc zV;+s?6~t=iodX9|5SQbNZjvhJr!PKnO|?WvyKIwSmLIW?8XY{F`~bX;hkI}NUgq9U zdxxhdy`Ag9?9H+QySUqL`4Ex$gY0|~-J+o!4>6;&RhtGYyu;X9;}shRD+?MJy`HQn zD?pK~)&nDouLVeyUJI0?tJ%@SU|#{X5~dLO@emiSCb5~ym`u6ujmWVF#D3x>uVrP= zVoHc^ohk0qkv&XrV@f8spG#hHwAL}pHh`}EMKCpuhqL`xE@i~ojs$?Bo`C ztNyb`xL*Far)j;vKHGb?hwHxH2s+R@Qd-uNW%VSWZte9MtGL%c*gHP`Ru`JLaHbJH zDaR}{UUZ_4ko0z$p8w*r&^wi#qn;)>+)SKa z$>G|ny3;GIis&!9$N^WGZyuv2Q@+Tpw1;Wf4C+7cGy82U2sl`-v>>B z+>GZD^E9EIL5H_-km;==j)3H{kz{`R>U4bz$HXwGeM!PH^x9$6fhexL!9~G2aZ9qD<5$V1j6Il@G z99`=Ou)&fwQBXF<$K$_Gvr>x@5_lh7wlvop9^q*p99AP>PWg3hqmyHYY>BL9EO770 zaE?itVOP^ZM2U4KBSElqE#1=3WP3A8;G9ws?mJ=;Dz9KJi=J2=|upYH7({cv=A z+SQ@Ogkj*a0V%~aqeF_?J98F0;7-qt5Bn;o+oC`iNEA$I>JScAs^g&f1!IDZg`)}^ z(LzdqpV@~NZsI`b>51wzfDL<&t_`>Pjd?Kc%kr|sZ7ris1`qX2$awBVaF@=22aak4~VN;;H{4n>65RUQ4 z0ytNel5|i_pz)Taxm5L+5RFSSwyQL3AX#a*AX=*L+M94TIPt#?%5XBsRD$F%)r1np zDnf?kp5a}2TI)U_VINlwxB|QKuX77StSqclRLk^c|F|7oOvENLkI^Pz`?K$_N3RDL z1Wh^7Xr^k&&Ui4J5$L-9{&@d!e|Haop)AQqF~Z~XEwm#KJiP-aS=J~bzCGVReEb28 zJ{sd{$g(CM9qjF$uH9f5R3UX+EvlF_T=t+nhls9pVQlq^dXiHZetqxv&0JO__wPoI9(Gj4Un^-^pr>I^f^d`s)%GIV?sP#yYH@@a%Ag-xcf7+f;W z1==K1t*mv|Qn1#9xm^eg<->!ia7I9hnLM7#M0x#;aw5p8<2ljdcQuGj`Hz#t`SmTH zdAg_++)8yWWfM@@CATlr^k|9~S7dg*PtiLOhd^7wV24jRUP-$B|#fE_eAbzRt!2?oC*ZPOY(hoLR zrshBV#N%+B@t&;ik>0WL4}@lNT?pB@NA}wW4x!W_VCAa~Zc(JQY9l-@q)4?fAmA4O zyl#M;Wzkc{ys?%p*Xe^Rmao@{Qi%m{Af;0AWOn6O{rBZ`f`q=W-~`rLP2E%%eNScC8eva zs1iNM%AUhRnnvg_LKtVed{*zta5iJWKH{VLHcWXoaN&y0MP0JMW7Faadr|N}&}A~z zFWKgmjHN@4d8t|=nHt}h+VnQ*wV`&$JcY{!9voX6+M_PU8CiohtvH>9Q;EU-!Okhi zdpKl0Ii-J!Qj&B?BIX^Eb~$EPh!=bZ#EQ!?P`-Tk-hnA8R|0!d+KxQ2*~6*C4Mkex z)fg9_ZqgM>G3~JJUX}ytZz$NG{FUxF!PTX)xt3b_zYdjA$e+~nH83OA#g0aFhXoIy zOK}C+>Wh8Mj1!u4HGtn;m@B$crOm77@y`BE|0(BlVUef()8nIq#!j6s(A3y6K@2&yN-;NIV2#2G*ZDjr9)Bnop zv;|asjii5i%3;Z@%|W{uEjXt90XAu!hrFJeA{UM__oF)3i3_U@CU*&D%QHUhIB!8` z-dId60BqCZ@2rIE$&^Ilb=rb3t<$eF3-o4q0_YMDE{$FAYe=OV@lI7%DM?ZU>E`sK zx}u(i#iatfcr6k|y33DPH_clEvEvPNVN<>`WmWJo5?3U-00eD0_!;yKZBEU^5$ON2 z%VC*gkoqT0z=}w=!i$K*W<3mTPQf`bju3PZ6;J!p24js{4~2?tK}!> zvW{p)*SzlT)N}Oogsb_f(2jtEhuw#KKR;K06x@FLB&D?YtazPM-Fk zAMWjTa-y}juD62ZXoo1Aefq9lDYS_)iHXsx&m!M#ycAw|c1EF!W6&s;HY!rW<(GWG zf+}mxpd$CzbYC8stVu-^_O_`M_Edq-_gBkxW!-L3O{vnvJJHIi45`HT5cBlgfEW8ZXd?~_ENG;iL$m-6OKbdQ z=%vtQvL3;898G5@!SVNZg)gcm3_7|U!bxx?4AzhXWh^_L z7ds#xVFCNIlQ*L)J1SFIlri(ow0F3Ab2l5E55~B+d3}2>4`RUchVO6n96YRdp|lx1 z{>Edj@GF5oa`(<|)Y;lF14X15A>>6IW2>;`Rm&Ys-m$Mm;1%xdnd(2!J@aa| z1zEsHgJrht1B6wIrFE#_k43I6)&1R{$rp4h|3Y_i#>`^1uA)8Dyh_rG@vi2HrbXVkm8tU# zSCq+)qYXh3fI31}XF1uRn`MjIt3@_}&BPGfCWGo7*g8KEO(*Ewrwp za(XorwS0cJN!`M8RTJ>9?dZzT7~iVj+B<32LzaP1nZlc8xp%5^FwAU+HvGWMmES*wvvqWR93QwVu+O}!;_S%h_ zOU#^DQ8MbL6PYAF61!j;gZGl4wmrQy5$=*{751+$?^ZRSg7$Fmc6mu%|IMy=LTPE4 zV(pM_H#YAq*^7gjD0MI$T<}Vj;8>?PFSIuv>hY2BAZg|N&1h)%aMSlT8d*BrGzi=> zxV!}P!|P-WNongeL}(T;CwRy!r!C1&m}{Yc2|IHsC0i8E>IjO9|!rmB&1Mm9Xedkyhr){InTE1hy=iN3_L-Im@vG;uC|*tQ)8 zLUz=@8x6n=o;5=mq%OufZTM`Ksi|x@__7qViwWc3!OcR;LnK{sQ`VDIQ+3+#1_hKt zT53@&TkkwuO8v<3kXR*nf;6}RW?%psO0xBft&ETO3Uh(@s-XzM8u98!iG$A;-?{%L4|zaRqtAm-lt?q{B*c#Cla1g1f} z8}dv8VG|YGqQkr#+}%u>CKjstL3NmvOb!{L^71(`SvY#AqgHSW(^y&FZqe=zhgUn( zt2>;96|FsZ0qo2}GnevZney|47v+#NTgPW*^<&1OjZQ}|@N7#X?E%uh{Pv6P=$;M? z*V{@+QR%Lw(p_0e%-fjO(RYYlqId6n5)@TApf<0qP>+0o`6QJ{z_c~df?B>4ClC+Y zVVE14D4AYSJ5emm4Pu^>8xpzy3XxO5vV7Dy>(v!bFH)nw&wCGOzlVNp#3u>AymsfX zUJ0NrIIV0SS#1yyL07PI^P&wtOZq1VDm-;QkHmHTjhDWO^}t@5u_N*lq<$ym`?KDe?L2tiQi2TX5@sI*`4TG=A(QhYVK1+pp^&TS=v*8HAQ1%5*eo zOR+?9w>47A+#PLFz-?i-tq6d4eMR2`txIX^DEZU8`1<=M;;L?Ls;Rdrb+rYR8{r)w zu;fw_c*CO9YP3u+>N2; z5tq`o-x}gvM0v~qM(0w2Jat^wjC*BD)syMzffwI85Kw84nG2ExwMRh(W4==@8^uz4+8vtEx$C{U z9gfG-cP;GsTQ+mHWD#op%Eu=IChbP9Dou4qzi3J6%EC*<`jaKGNBl>|nTGmo{txPMtmY2cnrQ2pWhu6v_A!Bo(1I75Wl`A4V`Ay~C zPF716nvqyoMojY(VYQIzROTE_j&Avy*5xG^E&G$k&PpGGZ>x-Cvnv8)9OKVJ%vN+B z#C)NL3u?*0WjeN>Ob{Zh%cPzc?SR!O2QRH4YZ#kFY)}KiRu&$E6T~DzScjb-_V*rb zHia43xb)b&fF)NRj)gWyRA-Dk_nuJWvm(n)Qx;RHxj4fT{E^G^#t%MXfkumIiDCkx z-27_6IAAV{YPnZA=PCbhnFg_o+hmP1SrLgY_}3Tq(h_I9)yiW$G{|}#N|6=UMnmO{ zYG@~!)YMzEZ3uBUfRg({RMfklHXs-~T}PDq)u(J%6N||3+&)B<0L|adG+}~6gh=12Wp#h|)&gE{*vX&z91bFu*%G$yV!P7oy zt>(?mPpR_IqD`F=Cr=-*p;NfkwN7=WAH@o2pfxK=kL<=Ft=*|1T8++?-3OWbfJw6_ zj98FbkgWPl{;XQ&hRycPlVu|6Kg=$Vsp6xjbaL$}Yw^WKM=j`HyEj2f`%2pxT+zX& z$;RV}5Cpclh3Eabgn zQ*qpsh_SM^8Wxsf=x8cb$FiyZ@st1hu!iz&PC#_n|7opeluNezF-sf>4wvfqOAVRg z?xfz)Hr@0JjC&49Ok^C1N6u+?TFwt*a%QSrq;Sr)*1J^INwiO)v_vhIl~hoG zV`=CvqRz-*m4P9)E8^$+MRVg}C68LKJEb+6PmFQbaoT)Drs1L>sR zM==sV;Pn!9ZH-YjoL>)K$w9WZ)k-d0(#?!H+G&N~C!+x06LaV-EdlR^40ijZOJxEd zAL~KwbhQBJ?8Vh9MRS5f6gErcCCA@>Pprg}>ofad3cXP{Nh7tBC1zWK+12oTG#E!i zGU-nhhFLj)%cka&7>ncJOipgXvw)zbk9IckbB|ltf#6Jq1gJKr6w( zD7$5f1}vcWNrFKr(Hao$y@+CkBY^xIklV?4bjdh5#oYbnFMrW6j+1)wr5BwH81^Rk zRfBIe-h%c}9^3~Qw!tgJxiLowWM4iP?*FC>87$cuEfZ`}9;T-yFIBLlc@_Gw)K$g~ zAi+xJl7?vYPV@OB0IRtBVSR`@_YJ%pF6N=a&1ZPKOITjiagJOkJySv>G9o&yd84kD30&g6cq8;qq33s1sgYn?C$U zefSTGH|n2RXa!;z0Q>iQ25NvRs!*hA#c5 zQm2dnsf|GGI-2a>LPO5|4RD(kIvZ6$J7~l%sd81=g2B%+G3MlMTaYZ!ijFv8WM}0x zByra&)Ycc!hU~HUAkrq}remtkYf0}mRHjjbKDhl@sgsEar|5PiMr63LHJ9bG7vhX~ z!b;(uF`za$GeI9r&ZehzzKg(!k5Z&j>&BNJ0x|vr2FXhP!g^`hG|`Z0XA$zfhQ93d z)A0Q3yl_f|El0`6Y0ZoE9AB3IWbppE`kvd7fc4#56lPRs9!!E${*<_4`?TN7{!!l$EQ1T6{Ukc>SC6YGJTi2p^hJ*4>&WZ<2mkg-gU&* zzz0*skdIr2PNkGQe9M^}S26oh^k^oP7HYH{BbjzuENjnEV%bCn>iTquC#x(9Iv=kR zyNNI82u!R>!7M@>8+9AY(Z2ekgW9tXvmgy_b>d?%Ms(b7jh?6^C>u)bD5II+)0G=; zdc(AQZ(P<%&|O8q-pUU(te59<&P5pET4M}& z5k3U(VZt?oRFfYkZ>IR=cotF)%f>nvrYLz4GphA#kVB0SLoG-5!wjX9#{}Z+ikID%a~P&@ zHehLMf(E|TOd7n0#89$x7My|>s{BZ6%PT9mNkZg@4O*u@)4%RJbS_$WYDsgQiqb08 za$%V*HXH_@>7FaiN+qyXxnXkn(C!fR#2Sk5+5VlcicGFy{|U!8SQ<vO(a*z!7#A?kzHFaeo z(Wdv9s4@ysc$o5GBN@$59Kyp=x$CnT*D)MjE3#UKhBxt0S6EJ7A5XnZcu7HdNC|j8 zuxwvi$`P8O@`8F88C~#Gk4 zZ)b~!`LXO`6573~^6XMe1v}Bn*4J=q&1-J7Bemtt9I$I4aG)nIrugPAUF#@%3{D+z zf>SH5WL-c`gSfRJi||5YPiy{`xKYsXqIXO1AP?@WLEX0Mk)PB5N z;*&nT3&v%$w46f%aqUnOdH*V)jZSS`LW{vbeBY|jbK|JLoG}V3%=U)aW-#0RCFOaR z>nQ3Cg;zTCMv^J*P@-N>pNnB2Def*r1292T1ktv~;k=X%CBz)DiueHt!;M34T{7|M z>aWh)xu)MGa!eJ;uh5bgsnf<&BYZDe^lhFKX&YINDKOYwIjAVAgG!&}pave}AdM;& zwNXWx7*?cwqH!5H+^rr~A{?2k^jQwe8%(20B{?e2=#}p0?kWT$zs=k*lt2)Lk!)Mu zXVJ;^HsM@iHJ6MYc7S}F5u$z3%<3vwJ^pO92m-S25(6nC;UJ~CjXI^&7MM;`9*qaD zd;MEnF3O#?bQC05<74lV^Jg5*8v3%VQmUj3cg3XhC|6I)>A{7P+@r3a$JV+95+XVK zZ(bC)Nm&(R<+>7PvjX{APj$InMBZaGVca%_iAXksoQ;l(4eSkB;A;fRifp&qn&^ui zBxRAGC5nfrqK_L1Qz8|%goiK&I)|+N2X{o?*8W6N>uzKczKH+fsTKPq4D$D(jmL-Q z%BnS%X(A2uiCu+?(@M6Kb90?hvQ5!zdO+zf!)xXA#cb!5f(PZLi?x>Y%nw?DnP^!( zjI6NUu*{c<4dg1qiXeXc+U9MykmK3?p@Mz@k%y!^Cb%cZ^{9~ zZyTEOHXk&>xp+y*;KN0q? zaZU4XG{HL=9|x8T@lOj?=tCa|lrr~E3zF+79|w`hhffQTN_RQ&{79HW%6!uGe9dbY z9|uv!(myS(^q_)wfo5`8@p0e<2mZ9ciCi-&@j-wU+SsSyP)S;?gv0uV)w^{M+h%K5 zDvR?Ua%n4;Z*Y&UZkCza{hYf>8drV@l~GV`eNQqL*Drl@nmMlGNiN@$88Wo5#N8QG_eTTusX5vM=rsy>9;Z`ewPJ1r3VY*n8r9_GykS~SP1 zBl<%||IyZiTt+LHu+G9qu9~=0g1bH%Sc+di*2oD1J|gZhEN!H{mo`cC6b2)0s?Cwg z(S)hM8TYS4DS3iuymHB0qbL=`NlMH%9HIv2MCE`ruXrm6Y2pOZIbXoA*%h8O)QzOP z`){b;UL;x2I%`0}_}Hr8EpEN2qZQmqDW?RE$`?_xr*Z!|sx?6PtToVeU#74K#QQ-* z&{?`3UQThhOdG3434lL3JJkBi$x+HX6inbInM7)YAFPkjd~n1rui!ka?TAlt?h8ye z9p;rfVt7VvG6iXrZ+nxA2$Jt!7A#t}B*rv;)?CG%M^n$!sN!Y^hdD47aPrl73ahl% z5#gah{Jzo@jT`t?#tFB+$a6G`H>zlqZv6Q2@QbXps@OVeY+%!ownE|v9c`2zb~GH6 zRAeQ;Yzv#-|8Q!OTeCPil%1gt$OsV6CgtC)z9+{LFpV03@gR*nC-tx|Ss1S?Z`h)) zWs9xPVffuVVqgiCXVTgZZvANr-F+zX9@ujtv&G~E`Y52nF-`8AmsrUECoo2=8ETZn z;^jri)g{ScD>0tYe3w`zB{;cqX|7va`HK;&Sk2b0T0Z{&g>Q_;6cJrX_AwXaoD<;x zH88#n47VKon#SV9?Jfs&FtQU}oIYeqkO^0c`&qS@+>69Z#jBVfgMXU;f`5)CFw#PF z`S;+xAIyD}zXIG$g$67vB|cS?nq{5~I+TV3g9{ZuQI8Q9gAeZnJt-FlY?U&ake&LP zJy@VgIuG!^IJ$|ploXayO$Y<2J4l2YT>mCcKs{3p?|MJ%9qjKMJ;nQh?)G^jW>=Z0 z`nOFMr)?JgwzEy1oEkfF`$kDh16Kvu=ZxOY7~zrN+Q$7-naT0z}=4X1*cE6t{WD(NbEZFARf2iiN?!Gkgd`8v@R zbDV38?h@4en=l}xp1@QD8FiSVAWZx6qHAh(c#-mFYtPSo6?F>t#0v5UpM@FQ-F2&9_nbeCZ_Bc?q2p zD*&UR!f!?5+s{w5kF;RC)1-`RiN+Z`o;t(A!Z8D0Zk9;Q7IvM31|kbs3!k&kAKA^nOJ}PEg|2r1(0ly8OZ`%KhE7fVhVq z%J%Q-Z<|TKN)nftPVi9N$h=2h-P9G(O*5W@ob&ITuM2@^Jb&RBv_H2(FL>CALpJz! zcTR5j`qR$QA>Tzk=zo8(*V~&MPZ3_8wOO=_g@@Q8bU5gZCU_I^rfb~{Cs4T*lLQQB`|3VT4N$ z$zq<47FRWht14slcL2vljDC2N4B(`V@HEyug660am8QON!n-HLk}r0}SKCTnptL!L z=M+CxLddOF4dig@_~30fx*7f{6ob)3-W+h2&B!s(`6E)ar;jGtD-E7Vx57AyC{CAF z=tbeS>aIl^`wJ96W5^-4Zt#(~#{0$4km3V6Qu2+haB?`je2M7if5UvNv|Xv@6ivDA z6bY_9MMCILk&-kh%ty+T z_7qnO)?MD?TH9)=V8HnO2cc9%UE;e853>TwTC;@WtpHU<(%v}VCl{|DNWoQ8fvF0$ha znwB!4*!nmS-62QH>m%KXcLtwviUx5e{w#) zy%?HZFlS26c&Q%^A?bo(X1|QaV<^M>y%YZG^?%ws?mxC2vX+*Azl@E5Ag0%>_xR|@ zHUW912PgVbl3^;~3a{EvGViOWLl8F}7&|W_ z-Z`FPMx+fxXx9;&q2JeR@@Yxz&5C2QYr&ZqeJPepq~5`U$fGx@&D+1?F^3q?I9#}cp3rcT%rXw|R?!nh)p<{Mxu?^^C*7tupV!OQ64rgJk=X1n zv?m4B3yUUORx5DWF5*(GLyUNk6}IAH8u2vSv6ls<6zIJ{^Y^?`8iSkQ=`6VJq`;n^ z1z^;fJx4UaOLdeoJv5u#Yo7TasSDaqmyS(pB}6jS^s%|=VW6Eh!%9cc?)W*e?pZMx*oxd+BME8J_on=6$s z8)cBnYv!uv+-XsztaUHW@rd;#qocL@Nr=cLLngRP>3uuAemR{DAJ$*%u=CQ}`>Ri8 z`&72*8v3&_OPFR}baaG#+gsZ!&EUy$ljROCwkoLxA;Kv%IW0+&5ZZ;QmUyzH)fGhA zi~~d(TQdn&m4dv~_SzM9Z7-=H^6eVC4M1kc;QCkSp&-x_aGflbF7TzgfaeHdfR9^o za5O*PTDZ?ylNwypZUvBbQkE$XS@Y_LfgK3?>)xjmSJtt=Y9}*hP{{8uvvM+%oL&Vz z=5%Uv*&S3bU7Zx%_UnO_mCx_%mqr;%n*E-1@lZ4ML+M8i$207`wdAbwK14~eF+2I+ z&k-u|tQ-E}PHYlkPG%gLl{rN>t7AF?vcD5=BE3F~pA_fup|@lM zcD~CcQp$jgavuP_sS5Nkv#b7iu{?f#nt9L_4NQC7?;Z{Lh!|6bt>aKpNH`M<`*SY- zUiwGR(EJ#EgkcL2y9Xw7zGy-Rv9CnjB;a;mJszZskg-PPw{2q61$SR7%{Gt4vB`>a zdSpo}#|p5$a=fch|F*F+#!_Usg?clRH>(Z4D+xo4#%8NFMw6Q<9#OcUxjcQrnlr)S zWqrTe_-mAa7q*)P7G`ny<{pV98?tyTb^xU|Uj)r~PL5KcSWM)!=q~b9}Ui%PK;IDX~8uTwgM( zJ;G!(^Y)$|9G_@JrqN73=8IhFzOtxn@0w+4cysW2I__WWPw?{K=;rnk4};xi5F$U` zBoto)>&Pv_qZ>?jck_$QYV*7OsFuPz@Zgx?KEbB^0A(&Idwlvg z6x0>5xMa%rvO}nuRt-NF!?HWPnciNn2aA?H9uCgmNb_GTX%`TI{%#Zq{#pvW7Z8y7 z;o#fvcK8E*)u#GdsSyW#%ID%9eTqJ`9xhNhx<0JKfT!Br#ncP`SP$o?;9dJ*SkJ+* z)~_)HO-G$&)08)F^t*2HBk(&ww5m8}0d5V~I-_e9o?cz8B(I$*9+W1xeLfK0z5^=ud8qKCgeUS5BTO9kevMq^Ja|}{tPIR00*xR0Z!OLS>Z>iM2DE+g;a&!>}%+38f~qRU>s zj54~Nj!$f(3}Di;TC-2)>j%VA-1?E`Pp4KqgO3A{@n$zT^*e-uE|F6rsm8y6;mg0= z|KiVbChC~WYPS?LuU>t}5HxKq z%$#0j&Xpl;75wSdcQxr?8T7H4UoZgQ+2hvXb+03|`}yoo4d8o=fd_mGU}1ndqnYPTk|~PXg10)12R7F1Lk`)`LTJZvs>MH3+lk#ekr zuF$o&-c0RTojaVFz%HmHYCWp_9K(fw9)$MP{O~8A?1KSbs($m4aLK^`C4imCPP|(@ zqVCGatC`wn<9QwyroN=ie?V6s%?rOD%X)P^9CGtyeuFHSt^70Hz(1YN)Us;ZyffK< z!}2ClS?=heFOtW$W+k#y0O+#>mh?2;ke9;PAHet~LD^2Hu9PDTvkLjcC)xESo;W(F z-Wq9=7m=l{{aJJ>lo_QnupXg>rxy z5~7^8{^jM=$gG&o-N7Y}SIV*{H@A4JHTVL2r=Jf%Bq9Qj7Z72pc>JvGL0(M~sU$Dh_%thAPeuj7<$hKSzC~P*{!rD1( z$D;pLqP8QASaa6Ij=rg@sIf9Kc>CSE6{8sTz}(LV8a$U;d&8p(&TFU7j`!&MrNy;> zR`)|pniOPH=kw1@Ig*hXvc{v7O)?RHje&acm zIE_5p-=#F!pIi*zccYa&xTPeLtpt&KU8$r-9Z}E~pVC)r`T4$BtnBNs)|q(cW^>j3 zz3E4Al+bOZo;6<4%i{tZbTEE*<+zlMhwZ|jTw$mGBbe$;E4&?|DLkVq%@!5*-nUmF zwrF1A=2JE{ygs7v491F--jxZ*#Na%or#)GPHy5vUf_Ufwipd8z9=%c$!1?VfIva zx-w@AqoLjVa!Ja)rPOEkLu0^b0GKZ=&F;YAe#PsluvJB-Z%uTh`?God6UQX&HW*pE zUY|SK>D9e?ky24TiaNF3ofY&axC-+pXxS8E0`^Q{`}&r&oAJxgf;tB{{-($ z;1dVzr(ZpZ-_Nh`69r$~yZ2Ckunga+K*0mf`scj|ukib={^)*wv4yW6Kr5f$|Awt` z&d~;bslEkgE&LV)dR+p{*}xAL6Wsd8$NeIjN&f%|Y9= zw$hz}B1UVP{}J$qi}&n~H=}s7|5AcO;EH+ziV~0hTG)kYD$Dyqkwb1oJ`m8dN=b(k z$aD`VEb7#qO)(P9!}-VLh76}aah05F_m;{NXChqB(w2NW8N7lc*q$nACs%`acps+R z2v?TbvZ9-~(N;R<2M(68>6Oz9+uX?N*4JRO<|}u+>Nzk1dhg_E|M}tG?y3>Ag1#DL z(aRlRaTQyx_O|gyx+9FBJ^wnq=o}8--gUH5FwDPC<~_nPKGQ%SH~SI}w!{C`Kws3y zOR(6xEL-HFLP@x4txM4-`Wf3$2J++O6?U?j(k4O$4&$J~#ZYO(=iTU@cH*yk(f%2a~;hc{#wU94>4oN-RZ`Es2?+3J_foN9EpX86>{5kosly z*bpqM&(r#;^sNZtJU#j0R~|5BM0(9qJ)rp?MsVPFPo@{VkqebsI3M{`Ps=eUlN?#j z(PSwusI@sPMC(PT`xuh#t7HBn>ftGVZ8;V7s3@sYUr8tO{rGmG-YuGa44(wX^hgCn zT^pMVjJI(OU#%%zdKa&jM<-8s&IWx+F%?Ug#7u?Y3hrH+H*8+a6U&mxwbnW<{Aavk&cK|L_?uuO6-FgX`rZ zJJ>~NtQDxBet;J!4U;q`{}XC^GYSsj7pc%s&0>Q%IMbE5AOKjXs3UQ?teg3#yY#Cp3PiFAVzaudPME>g;-?%?wE)6j3jPo{;z#cM3= z$S4>p(8K$G5(3FP5>>!9C0XgTvu7fqrv=BEa(3bj;#wb6^E)c2HtQ51cDev^tMd|$ zNE1A*g}(o9I3mrhtX5ET!4HZ$8pr!g|Av=cHKqy2MX&fOSHKMW~+HNHD3K#5=K<_BBtWk|aPbk~g@)QEc*;li!6f(U9`1OL{L7Pz?<0)QM zcEqeHBqy!fwpGwetfL*#RH6Z%PTZ3u=e1J#g6d3JD5a|^h2B|N|L94C^)Q0hVd8>1 zJlMA#Tv>9gt7-Tl;1#}3fiW13m0nbha(>{`n|!75aQAaN4uaVNimT#Nk9L5W3@Ojy zMJJ;X7_foKV{)dRA5_+xOquk!KTDz;vmeW~IAPME1Jlw+*n%I}oopo=1OSho3i^pu z#_l)Wk#>T{8pq1@;qrXpMF;0*WI;+xpX0KO^e(nQa=>bI?*VWrPt@{AkiVs@>GJct zq@AOqjZ& z!J!6DJ+`WjkiMQ;{K5rMDb;d-mo_q+n|D`G!4oQ*3)lAU%>zCed4miL5z1s@7Hs{= zsBBXw`b1+oLZCgBv&N1bHgafn{jkIRhFGYfuURiGTaec6huAY=>cvIB#>OL2e$M~! zp6cxOawvn?+K_c!>w*Wl-rrEX%D*|2lmy!>Ijh5APH2ff@jLFZKr%Ao1j`fT;Ld;- z%P&Ys(-n&A4oQ#K$c8dNgU`Scj)B$>g27lq3Wl-@NymD_vBZ_(JSR#C8-4{ZP`MBZ zoq`=BESz8L=ury-zg*lOk7oSntn3akIn-yyQEBJ6df*jE{XtK6y>Qom^wg_0TImT2 zw>Bc*D%RF)%8lTc2gGb2b)bBPTciKn!v<(WTW^g|Z0 z)Gm!Xj>vG2&J)sculFV?c@c42CYXVlhQD;NL6gH?R`1XyTaGZf!7uB}!KRQ$BRzTD z3trUO+c|RD;ybK8Cn}8(;o3@MD`6#FzF?k@9(7uP)$o}&m=J=k>u>#aGQUX50VY}& z%|^B;)SF#wQO0cj2eg`-;pLTD-o)akZ~&q2gAF)V@;!kTo9UedZk}o3=2zx#%0^mb zNkbJ_f*g86P$`g($qaGKDo0SFUZvY3xFwXn$QL_n)R ziH1fGuCBB#cG%zEU0I;|#X{PMqkvV_C0keSI~q9ZssCv9&7DwuZa2`6y_nn$B>sh5 zR&g9&PcPZ0DMnVip|X8@Ux%El=M8#O-~JBR2DM{5sF!ApWO^4e1WD4?8iV-2A=q8^ z#wZMap*-!-K>uT%kyx==z+n^P5R`q|9FpUph^_gRB1KwLgK|L^jn>pmB*STXaF<6w zIC*ka$a#NZ*9CMbrbBk*u!L&eW85Mnl%r-8esRpO)sS=M25Ib{4-qX2VT`FNiiO81 z1gyoI!A1a)ux#{mLk+v~VsHy*j{>E39km6}2=f7(9-TzM7a)T4HxRl%(IFna>TaI) zk00-yZd&|F3R5wE4|`hL9yQl3gG^T=TdGS$0Z#av_!H@uv$I z9I~7*)HYcS)G@ri9A1o|VP;L_FsJ0|eh+U==6Q3@zeVmK!tMlH%}yC@2Ifv5_YR*u zIq6A3tgxG5uYY@k3+wE5TMP3jQ%m7jk2?(B2KP~~7Vr58T<*?`dZ-jNkwt6HF zne%QkNw7qPHw4b+8^-o!ZwAOciQ1ymj}_?3PBF1bohy>l0k!BtI@xcB=ikU6N>bz` zE~%gaE4QSge`~5fw{5(_+#ZEUmg}Xkz;vs9T?E} zAEg$8m5E{k1d29TociU<&{b*4{U-;9ZGp?$-=sa4InjAX|LQ*h({;*3l&T z&W3$DExj zcut*Um9Ms6ky_~|u_XW-)`Q}FmEOa4z*@R)y|Xh!ADu)obXa-tMV>5MX`N&*FK40J zMe&}Z6ir)>ZO&h6&N{-jDWP{ClM;k1UE3lxqX3cGfwh!krP86 zwI!?lC)j9lsE3ygaCqB+r~BYFu6HJXECJC3s)15FOfD0)d5yyp!7l}+^e0tIhJ6-Z zt?(AtarHPj^N7VB$?HCwz=?_OQ{!+{m1e{=$YAR7C8!|x7n==|(YfmiUh?5Ly+5a^ zflKU@>C4d=Za|o_Byv)E@~TN4s`X9#^mf0v-}_$*Ew!KE27P?Ug+8bj^s`&KDY~- z9G#D6tt~Pm8Z<%A8=wu!;q8ueiy2;lLR0oM4m8;B??kf@G)R_sVVUMI!{w6uwO86k zal3SKf()U>&tyXXSxd=8h71=)15g`dG)>?!!aQhgvO~IJN%uJIn#{0+o>A{6pCtTH z54C6|{eWqS_ym_at5|>*vnxF1HtR}pUW#M)!o%$jgN>8%iKfp7JR%@2#U;vt02%!Q@aSvI)~-qXGBMc&sJ@0o_>;ENnurI}UDP-*aZ za~bo}&`SSR!_LjxD&W955R~A_0P%h5n!ox_@mksK`CxVnUHr{(vfcVN%f1fbG4bvVxO3<5yP74FL(xUe2Jf*8tNI=(qsf;SUwv%} z96ZOe#?jUx*3@=K;FV)50akyhvksIG>|}{0xqNw8u`K=p)q-x5FChw;*C2k0;o;)D z8iP{mF>-wGm}VIvE9n`@+zUwGS|mMfY&F%aqo_I)WY>;zE1{{lhKsy~ z4R)cVfE z7GBqJUg9C5WD@BQXVo&MZM-JD_+|cpF)vA^g(saSX4+YK0+e)V@4B`+GMfuFKxszv zh&DD*SX&l_kfgNnGjSaf0Y`<0I$@s2o6ZbWNaiO@i-U&2U!tom@=M6hr@g4ON=cH} z=drP>XkKhVM{$~~NLsQ1UerV5K6&Hjzyd_6|D-%kBSJ5(kypO9zS78Bccjo$Dlf%O;oJOpm$oR=fw27-#LfJOlG(qz4~pjio0C9F&$Rfmgp}S`B>9 z32GyjEf31pB9_uXc(T4)RT-sI(~?H$8q)1sv{qlLvgZxbNjvfoG0!hX*VS{;!OhnR z@DxpnitxWf&Se#r0Apg=sqzfBV7ltPwzZ*uYyHF4oqf?S=M_u^)m#1ws0A7ykRzh@ z!uPZtaviCboS>tqEISM<=?a_~RPYkt+1ZJI*Z$+nZxA#!n?_Gxf8;jm>g@&8kB=Pt z17KfKqO3aeS(RkR4N-D4t-FqzFaKZm-gY^zRlPz+$V+Wa{3bs*G=06~yM z7zB6#P_(V1L4ZJ$>?5EXg9b=J4*SP_qIrh7FLu{jnOPsZ_wH^`wCtHVVTPMaDV*^cEQBi$@%5`lX%(cY|SYbwZEmZ@o!%ATWS^7TK9EoFS}(Rr~d;X z+_fZdwNYE^|NY_blrkNSQ_63#D*CcVn!G?m>cwvxb$>$~s*=F3t7KAQKBUDEQ}g-+ zUt|n6^o{;Sb0#7`3K zX14#PI5Z|+l)R}&PdldHidgPO_XAm%{a3ry0kMMlF0C_jJhFe#nmGDBMYy#RfY zjALx+P;YFk2mmHfD~P~@VVULZ@Wh~kL9l8OaEi@l>FK}%^};*WBu3$J`|$p{qWGB2 zf|Aq#+k!JA&Kwox@(DK3jOLI-Y6S&hqUEB<_V}zQU>F9Cxv`_um;z9;D-WNR>oYY|@!qnuw*pj*U86KQk`>QsT z?kjbKco+TGxJq&Gw_daXe?5Zj36B3OEg)`;Jb=GjBF?wCNPo7+_z`d5sm}eH%3gvC z<9&r7L?~V2P8LXN06*}OA?XeJkb~{z1sgXIhXqQ`m~+O1*kZ`_*Zq#ild~aJwvpLO z2q$Z5%p9nQldkPdh=mH4t(~L$HnC(4Bu;jbg=}3>40~e3z5%T8er}w>kOtU%0C||f z;cn?RpsWo1nqBz@>H+thuB&BRXD(L~o2mOR@r3%(&i-EgwpmI>s4X97K3nz%(QmLr zo$6sS57YhfJ>L6brhh+HnId8P3@@1}|2?kiTU|RQvfmiycL)O^m>x&KKK^#^IN^a? zL-#YXeKm@S$fsQXdhg}#<{qNe3|3!)nc>9-q&K&YcD~&ntjhL`H*oIlGIa`6kR+2-9N=d26I}M+-xDEL6V41;!ugEXx>PNrg1J;eAZB|w z9_6*0^XoOSaBEYAVHyv~(`E{D`mnu%oYCi@25)1qMJHygzYJng&JELDco}((GezEh zNpRbjgWxg!J{#IPf*Ij>O#pdOS2oaCaU{&v0P z=9#@!k1)FwDw?RI%N#uTx}D1HK`&UQ+}8V(@jK!>@3AlASMv^BP}K3$A7i$5s_OAQ zw+{vRkB>Qrg4%^^i$s2{!X=?VX8KkUUJ<=>E&bXZBBG~rJ4nU=u!B0&HmJp3d`<)M z&e3}p;v9QmhuDW0>w1&K^aitj=qZWce#Ip$gEAK^K2(y^THfQpbhqlcRBT#uN*64X ze)j5#qhbo0s^+IiGNf`hATyV|;WOfHN6{-HUJJBnJg{OF^^Qs^(?GftP6Oo>L$DoV zjssU)V)j0F#icEkg_sas5Krg{3kTY8nzzDAjmg7Cyr-&A> z$9DStez3~vTWco?YZm4tiNdoLz46n=(NUfQ&8Ek(v~Xr==l`r>9Kx&)JxQ zWOT(5NG_>x#4&v2EM+ZmY4y%T{J-krxb>&6!qcAqq`jxUxf2>b8Hskzt3y<;DUbQUWbSmJP+HyM^P z@Z#|jx4M^Y$yUh62#EdlZyA75;#hx3KK)P+2Btc~urQ9^S*ak;awNjq^pc&lb`e@n3 zj1GCHYZ`J#+#H_7%_S~f0AhSgx$mQFNpRG;>p%l0pGwG*$rg$UVw|9tl1`G3fxPCR zkX^?+g1!NeDv-X?q~vyh{)N^z0}K>NK0N;Db<62Lk9v@xf@2l)qNl{h$`i7wltWku zTkyEcO6YQDj*M!>v14`oL>w0^mWy%3fG66=vRm$VW-@y_)0BU`es3nff0=i2gR*Rh zvb&7xfdc+eCs{nMjp4Ma^9S^fGqvQk*@@<~ZlFQ#?wY`P5;4@|Fl0ytXlycTF$k=M zYTDrdfp6?EqGzhlukoNEBRxokUatSCGDQ2HIz;28Z`zC%L47% zwl@4~#e4b%g5`(;aKV8iu+c%@IF8AfdhA|9Cvb9MH9*Jq;@BU*4PBi%@UUEv9cJ?& z!pctJhFSD!4Bw;xV{ATF=ab53oTp~ix%QJ!VWevW)-p8o@3d5JOH@#+LQkb#X{lzI zGSt%YjHX9i1RjC8qzCz4V7;BioCjQ5#Q5It&`W)2gjk`|>U)q?(nfFNQ^l%|W{;?1 z8toZzXN3UPMMFRvw%D4!F?%!FI<^K?scQ2h=C>M%GHgGR@?@V~t*5zYd%uSCvbw%4 z6m!htpCF{!>;k30%CeIDy%Sg|RgXoEj?r!rC}4W4Qn8*TNd*%<4AJ@09m7LYze#6Z zITIfqO|ph(*0>nCArA%+ppJh{K(UssK>Sr?;E2_MS)$RUF7B*c7Rc2i6WvX!hhby= z77nMOO* zhCbDyZB&}oa+zvmFaZE1mq~?bUU8Y{D@D%BZtwB0IZGmN^jl?X{?}H-^?UwlEMeoG z(k4(771>{QaRrMT7q8D@L(Cf$^fv`V;iG5Xwc+x^VuI*GSg%&8LdKVUu z4+ec&YdAQYvnY zu^l#*<7xDVS%jVr&FcOeiKhi0ePD#Hjvc88PVqzEH;GP|D+a12cu6WJ8xF+-gjz}g| z3EEk~V6++18FofsXEwjdNsN4is8@@|xs#R?93sQx@}qzoE=%KIj(ua+RGooVz6gP; z%|&d`O1I1f-u9eTF4M|D?O!>~>;`J6LZdY4;?TaNUW0AW(#!OMCF)Y^9 zS~THy%2tTG+s|-XzYt;r={(=cn@(}d*{$d+)*vq%7TSHrx?2Fb8%Od78hH$^5gxY= z7mp+WMt0Msbu#)1cPpSc{ay-?oqe~$NoVHH4D;}j$v2O=?J(IZW`IshfXLSqY|@sr z$w$*m0NR^eH_^fE2Dxj2>nw|2C=}le9p!HuEQO?Wb>HRacVSOX+w<57HWLONHuTbP z@BK63k4f*}d1kh`;zo?gDB zh@gcfjhQOT<`NR!Htba)Go)0d_5=#I$PH3yu?s*}1Ojxn$hiYX1 z-1)D>u)TRs<*2OJu^PUMVs99C@oI`BEtGve8CzI@SH835LdIa{ef<_EAaML@FD8xo zERdvbz|+q})m|c={{l4sg&z5)t&CGiggZFkp@~eJ((0Hws^L}Bjxz>LDB}a`P+z0n zWi0J}O6)(z3YSkx@hAcm*q{d`#180)7Z1T3@&q<&NVE5!D4wn6YTZ}b^(bqYr9KM@ zIL~ru&@)bwl>)I_4qzg5`$1jFc%mDWN~+z~s6Sc&{F78#bfTXrcPcVnqbA!7q$rz*+E=IOf$%M$kUxvDrX0qm>jxNO!1g<-F89%g0V}IUCy*kw#rqp;D2l-Di~D!G(G9kSX}z669?t7A6!7 zQw`cO`-^lA!5)-L&`A3-_mHbf6&jJB0e5MjkUm1EAMS_6#-^cOerW|Q^_xa5Q}+y^ zv=0Mt!_lyAoA&~<6vYG6qzD=Yz6vfBn9hhiCuta#%Nb=yE19WSd~nQvsX(el>kG#p z+AVWT{yMo@vx3~p)&*>@XozEBHbyk2nCehvpz>)fO)iw92`sE+$p`|7)2-at z(IhnMbY!iiR3)s#IEkaSo%)3;dBHTnW6fJ9QzdT?EgrM14q1J}V7r2%MZZeH34A%Ta6Td=MeUjDo;RlQPF+!iy2b;+@A}{o zTy=$2iOBlJnwk3H!DD~AzZ#;&ny^n44bOL{0l2q!CAX~Eq5h7dgJ=?{SZom z(6xLym2Hsy_`K|=WmI&wq(o@r2A)LOuNhKX`N*9|Y%AG9ZOd2>80rzPf<*#(S>G;) z4HNY_-Z5`pV>@IJ*O&DzHeQp$MCjd15ub`r`xz0t>}yoN*=varY;M~4yl>wr<{bQ? zF>!_$FizODHir5{@UI)5efMyJiPr;-*R7%w>;a>kFdXX@s+@4i8#`x$;3u9Lq&My9 zRtG^U5%6$$#;7lbMx=-cmUMEBS%FwoL#v}$G>ld0T^x;z4^=B8Mv0;yFQYH$wL+l@ zMT;<2{PY`AP637uBP|r|i$6|J&d+%eq|`k;+30T?;}+rG4+v;C<$c1v_lR?YzaNJu zmy^R@0P9$ub&wEK!LHxK@ZS23{*!+T|r zOenRz_iSfx8#glw)Ck5`&LwU_8#xQ+C#HDW85Piv*pi>{SihqA%D2_eEZ3(UG{ql zuE*=?>iPDcwzqKUh$I{SI6S?fFB>phv z|NL@)XYXjR8semeux0F;5XE`b7UB@^@GdLWU1w(^wX^ldS1*Co==6u1OC;>>Z`x>+ zmwmL6M8w72r0LPY=HB7U{R8XcHC%_LmpEv)(y#Ush#O4^OD;G7*u9%1+&2B4XYkI) z6Wm6`-X^1Gh(Fs;Xuq4F{msL}ooAW}}}iZ~KA%Zzd;KUA-tU!s=_6c^~u< zmb1FdYtvKrQYE{>Ud3f_{$*ZhV7fGB)o@{~yy&G>R5@Dm^h-KYUq1(0e>NDgp(EB{ zQmVW{6G8?{#g2N0?^4xr%az}dMBJzI?r*wkQKM=;lsV0GLG-oQMs5Fyd$rXoA%*QH zj{DR@T=N-DRdvWZzrwqD5h4o77y7gG6rmci*DHXN?&__%^RD zZf%s47s5g?m~uPC>(8@LgvDrz+rNk~vpn3x=Q}Se>ADDOd`S5&_dn*=EPF6|uX`_sJuo~#SOd!L~KHJTpYytz2R zR--EU{A8N(Urj}(m?mCr`{nMzVF|phLS+ZRu}_!c&gVw{R8+lNqpH)H^%X6GzN zyZ`pA-!L>SH%o}qsoy^Nh9d^HpN03FA(8|N4huivt{@47Z${dF*efW7xSlG&XK7Ua zYWVgB(E_{6AE*G|zh#|rpV<#RR)aV=+&(&fwYPI*ffx*Wlo0bwRm7w+!Vbo8hHlIH zd=|c_Uu@hWpqbZVj>);BiF-(Rhp>!ZLou}W`O!y^U^|^whn1uYXM>}i7uyzvM>+l! z3}PG(uN0A>fY{GLWw8A$pS@kX-^2f`D6vxYwFkfd!yoi4O;1hZt)8zO8kX3!a>sgs z*r+M7AM3UCz*l5n>dOjLpe@;}P&ZK}q5+6m!-oIqCSvJ;88V(;AQbLqk$$-u@({^N z@(q(e-G!Eb!xjHwG>cHQy%{)~mZ20Px3`+K@ARv^2ICVT9tCxE{EGVBWldFHqF{hp z1}Ggc(0X?9Ah*yXx9l4+eLh+gL*dEE#mPG=xO3wtK-k%?Ye6{+GohD!7nJ0i!w?)1 zF=~m@!!syOC+ADnKuqPyl0{!bL0!=P6W=kvg7CjXQOJ}#l>Vcx8!s9N+qx5x-N|%W zWS&uBS`rnNT^@54zYFTBumtYu*_Z`*tK74jF;1Z7^03Ms;C%|j7h0z1RM~*Lxw#Cwsr0iul|EchdYgvVzi2l*m%6*dlMlmX%5g2D@nSwS zm7?`xaj`8#!dj-BeDjtmXs>)PQ<7Y~yT6EpT7Myr7MG-rU|Yt8MLlB4Wje%i_HfUF z{;(jQl3GwqTs9eNgY459-G&}}u`9RYW>AQIQ<2$^ghlSCz;TVPe7v?K&DVhC|&OGP3 zhb8YQ1CwqnX@76oaR;m5$?jes$dsB)40c8@%a8pWm}Q}`vsoin&&>|m%v@zA&kb!S z=lM+gRsM_&rb*_Yr1+sRjgO++Cmu(? z>Us5iTU3G!;QV(hbgNM?EYzbZZLW7}ghnKyBld}^<$%E4? z=onc>#Q1XV`JCf34|FjobBpnjv0@}r*-U;TBr<5m9<1WQ)BRO;Gy0smY54~@Uqk^) z|KO>7E>rMTPTZJw($!lv1ecn<(SeEs(%VtC!W$MRU=15;!nzCm(@ z3%)HZKf1m_&}#Os(XXln z6OzE{k2{-(jwpJh%GOFtaXjo$h++{i4`t{0Hfd2&an8)flrxJPm9<#n)UP>)AT zjQWm8JOFy@z;{p#j(Ai)JaIjU08kP#g zcT5*ebw1*^Ld#-<@`D`r3K}b4mLgZr2N?)2f|_;n1RD~@7=EH>TW63cM=88Mm(+b- z$A!nIRiG&IpL7@ZQ|6et^5QDeH-5+iZMfNS3$-d^d`ibfT>Ki3=;AHib3QNiafKi~ zT!UD;NpU-0^i!~cCp%b?XKiG}FlSc`8H=akF4wyGgIMx~QJTAxyu)ZB%OS)8EZu9X zFcRLNo-ZUD6(i_$yDL8}!lq`ce-KVQd0bt`iD^ZLdTQa(jia3{p9QT%sVcV-$NFkC z%(Qo!LNhJHg_`6Wp7lQ1i-Em!9BDH&bdSfadS;Msl`Hw>WU9a44$p1D%rjsE9tkxc zS#!Myy@$Pj>;0kk;P<^pc<=@66D``LaSl!UjDO(#G#L@i-?hWxq*+ey&(f%wk} z(t1_dccMGv*YasR;L*KLSRMWLEdB9FDq0Vx;!0k>7rzGaBlahpT=~>E=}LSf#Kov` zS*&=6B%o+Kt2RsZ!yGTq=y)j+2N+hC?g?vEH37=y4GMV$cziR+8GUv61%l|(m7cj@ z&*sLXNfm2x+cQI2(VRKXb-AHDZHRi`GKhC$`s5me=9t0W6T)Sx!@w1f z=j@D^XBV3(r+j$FtI^~4&RmoU@@WiUXuS7FRDxk&-CXk_;$F?y7T1F9 ztyMSNdHWfBG1xg#esP8CTkKqldUQ7;`eh5ww{u1hm5k$UebT>J;bBUHb^|j~&4vnK-0Fp8e{3lLP3iK5EQY*LoW6@PCMh}!B{W*ey>20S$$G8YPHbX*TUk=2_5(F4FN7n% zBsvI}*v1Az)M97-`(ThKv4CN;+f8{s0tNtXr zKB$Voy|*k9{O|04ttO@7*?c(XLqj;JbhWtN!)xq=2+W#tcK|VE`*?%E^uaOzt=cfa zOlF+|C6EY9jv=B#-&6>@!e8WnUFC<)R>SyOkW89c%LM>DJ4!<9OjV=7G30+fRF>US zz3$O^4&Qe;LLS+mg*QRP~1Hr6y`gfyfzHV(xYaNdTmSD&PcM zNP`Dy%*OrkT8KrrKJtKI+OtwzFi@qpH>tkG54LEkv|-HgMO|0?m9hT!!K%z8{uY$1 z5Xu?+wWx1%RJvM`I{51MnZ*_rvFd_96|s_b7^@kSqEf#J3pCOu1yL( z44?BFrj1}RRJL%|g);G`gZvxZ)K<9c)O1jOMTL1`NDX{8Uf~JD6?N{5vz5IUtG)V+ zXzzucEY|3s9yh6q}~CdZbRVW@b2gW zM;x#Dr0sR@6;?JH;O(s|ggNoEf`&QCQwtv(PpQrs0$7S~krUzwV$5&_lI4-|QHZ=a z`BC@{3@@&&QGknYU4q`>edS^j`uWq)G^zUS@buB(uRmg!8e6gckG`T5h|*lTMqS2Brvwi>Hqqxm?(~ZS5z0Q#+z_JR1K2RfI5q7~IsLchFLq?=le0ur;RCq$ry@2K0fR zT^>!pHLJYUKy9?>(}<-+e*X?LAaJN!&)@PYvqjD5uLLY0oAlfBJ7Lyaq3mF+6x1~~ zj~Q+yh+~5kj!598wbly1o8TYHb__07=GQdM-bs<1>i zK~QwjhSs~BY#gCOqKq1=N@bIN6l)A!;;}djrIM^Y2*~bbC9}2l=$Lz`;#Ul3YEW8q z*D5a28g(MUZ@Rw2V>7n`opB({4j_EJMkOh#e=nkfm2da}{f zYTmG#?(^{m^_rJR7%{tTjBSs0>~^Zgfanw4a41x1~P| zz3HHdU$N6A!J^FL4jEL>Y<|fbmw#+C~61%5?&T zUZDw&g)yyinAFd+JWANf!95j;;f%Rr%T}N+MWmoLtC08QFPueK8@~O^iM>!=NlZ4E za+V(aynwIa&f30wLJG~SKrp%^dZzxudQvxx{t;(GOIc*Wu`MGJ(xObP!GuhG~dln#__; zxre}EP})=_(HP(Pn55clW(u<3jbIu5lB|HCW`n5hr%xH4txR?Fjky4q(MkLS_Fr$1 zAVtyu!CNJ+cG#4UgJeW>d*6rYef)3@<@-POf9ly^*i|vJyx7} z1S!d|aE;5Ejm;C26xb|Bt7H$m!k#`x% zV2L*7yGpw*1MeRUFL05I1zOBih#sc;VWe1D?f273P~zGUw7y|W`J4X_xnT&uQ&YF@^w`Nsxm#efIUW282CtA62g&0CTL;SxC0Z3CvN_6U4~ENcq) zL5z6#y|&)-c1^{K9V7pClHXB^OK9B#z%J6USjy(z+=+dpyr~}hlZ-p}+Xh<;6dLpz zOALjVv>?YE#RbEG68~U|`vhQTJ7%ww#htHb248G3hvfYTE4TB+AsKhibL{qlHU!j-V3(2#w>)pX&Hoa7RB$yV|!5FuhTFt8qke- zBUb~|0Qx2itjzK9$F++7%^5%#6ghYZo&v4=&B^Hxyk!{gHo1}CF5U4AYLmrm7x0eP zU3%`E??TOT6uZB1m2vyvVElRQ;nqU| z)+R`T9TyyI;l3gJF|TVWV1beyCABG`2Mu{mL8ZCY6=`du5c_O#7m*^?5;(cKI~NE{ zQJMjRW=movAacdJ6jbNm)^B9b^e1o#MbSmW&{>(akPJVGP;_s zDVwT`7cIEI7)=$UQNcvP?CQ99J}WY^JTuefnzOVT>Q{BSX72r7zOWAE8dHG2o-2s@ z#&6u^(&VCohV#Mc5RUR^2=F4{=>QPsb4C)?r5)wuOFMyLi)_gD`k8yZ^_J3Mbm11k z!(H0at5!Vd1D8FC~j9&(}U<9dCXVUc}u5_Q= z6y1cgWN~H@Mcmf#1h-DNCYQG+xFgeb)MF1zhVXAWC#1w;k{n_w($u>FWw3vILb~*} zf4s!`2fklUJ|YI4Yipd2KWkBJH0Hu(ZfoKaDHcEPpta~iI%}=chJMII&7xckD6$5T z$&Vzvcr?X?JX>WPRgwq@Pt}@`IuS@$aKW;78K>*RgTu9#uxlJm@T>O>bulzsH|Gel zI_!Nr8Q})cOGFsyS(#^($-DF6ABVTS-4m?fC~$tm;p4*7M?h=9C|T|jfCmXW5q*IY zeI~mW$LJ*kHjp#%Vel@d2$abRikZkt8r%Nct08u)`iPO#+t|)5c|*e_n+-<+X|r&ld$>ew<6wDT_w1oSNjW z9(k&F#STG+2CdX0);UvHWTsAeTghl03t`Z)$Waw_I+37-F(PjSZ|?Ywo4jddOFS2u zEcg~!w+#4;G&iPe?2{TVXj66^!451oe)Km6%oL`3r0k%f+kY|b=?X&B?XghCqtYuH zRYlE}>nnr?cYax!Kk#_n`1N8;8TJ<6zg^A0eeKS)1j|z{6~}L_`c| z*eth{LumL(o=)Bnk0Bz9?!qAp^5^yF?E1Yo<%e)qvQO{A!Rp&9TF7}ZP4Acw@Jxs4~a8No-2u-YUaDx}8{&&LoF*cveBFv5IKbyjgDy6{j z%OZV6C$E1t)>^xkWQ+6R+kYayvtfI?(T=j+Gcc7nUoZz#!Jjt^1I(g%80G%;Fkdhq z8;H-&$W-7LruZ`RGT~+tyJcpk62CC^3+87+%MhQPp{c+>6mJF{&|NM56Uq<{xF056 z2u=|XlbVEQ#O+K7`WH)?#(`8;$0mn`9U!v{J!j+SQDIcf_JG1wKmcJ3NI0#>*4@?B z**DYuCOmYB;#zvv(V|27<~b2ey?Mi{Ug$}#(ErJL^rm0CnBIHSubfPA26>L_o{Ngz zYK=DyQ#X>6W55$!3xD`z?WFf69TBrYhfdJ2C6;Ckm}rWK{+;#H8x-h#q5PNcN_5su zPejbrO*x%)-TUa%UDWhWNa3S43U{dJh7}M3-mz}a68N~%xZ`IBRKm&tP&Q67|5&Uf z4+c`y@)oSj(rubwZCHj|nq&b@xZI-Gi3a((Y`aTm z`Lu4ty-`el*{2hGbxziusYldL^RNW&c`*KY*XO{g8`N-)ghgk!+=5Imynvso=>VcL z9v`qqXnjQJZpYg4mcshgIK@kuMWf8@=mos$86v+C^uE!dOO<%bu=pP@I{@uWb#Vih z;nXMH*g!%a>?opmHHih3ntL17IvXnQTllz&?h{;|eap*{^J^;LdZUM`s*L${8UMYl zD%0oJRMclgZ*Qv|dGjGeO!qbjk=F?UciC-dN`mPk`~JAI(!o%-W6Cj&*)_t{&DnW8 zi*&^7P4i;TymKfmbK#R<;YQ9Jd@Xk=yta8AtqW9SE(M7XKNz4eG#$lbWisIUkR(?a z|IYq0v*&I*)4fU9BO3hRMgwyj9^l1VcPHK|pLOfoTPu1vh4zoO7Q8|RUr6Vw^)3Jm zppAa(2`%rF=IL@6+T(Jayy>B^Gj?|X?QhI=!cM`YZPf0Ld-=v)XDga5Z?L4uQm)S{1STnOsO89JQKIjYNIw*b&Ply+|y7v~w z#lSC2kGBX??LI913;Tc#FM=|7%6F+6AG-a1*?WV>I6|9kH$+XeCL(B#bhH+VZo%yx z9fb<(@~hd|)<$`lhW_+M(~x@Uc)rBPPw5j4(y3WWv3IQjDvf#qS)LbHTvMER6?Wcv zqLsHc^z_4-mSr4K$}%ogFZBYh+oW;IJU;t%OM0B>nnS_`@U1&sL-BsIn8wH^k+)&0 z6Rq2JZu0nc=gAt13MDV2^9uhB#Tm1c4?_FR5GRIbYd}ed_|LOr>}QvO<1IdWRndbg zj3K#af#+LD@cBsddC9Ri*u(46(ffiU_BwbSr`_~1tq8ETdGq%A>g3&Y@8;ryH_n3X z>w#T{2BSe610lfSmk+W#8^+sYX$}9o(f4ebxq})bV;PQoqhA*x?qw7~XvTr7q%#Af6RWJ^(u1X1z&APIr_`FQ8{VmVW|2}AM z&e=Op_hQ7wwE!3EP^~IOYCoQX?ck4e5vz$n%#qRbTsO6rT@9(5Y(Md}5Uwm%+o(6w z9Snc7T`P$UngVBsrVeb;tUUN@GsVNuK!hLnJdZ=1C0esW_7gw-+@m~*Q5FW}svcxY zJUl}wPpAsEGWE@r-q=2)Ax;yQua^xbT|F0!y=^UcTMPOtwF^rY3N!|Q3=4#k;+vDw z3MpkWrxzOL0_0;q(}B_qIcG_SH7ef0YwmGBp4?vQC6kUh^aAhpOy> z<~dE>NVaH6ne8GSu`|70U#+fN4yj*zkrn8QaH>O`L>??hMJgO}9mtYq{hq+^pFXZ^ z;+55Rec*|!I%}){F}8^27GCih=ii8aPOxvUSJu9muBU(=mwY_}+T?UCwKQe0_Vq@@ z54|RkiXeD8&wcN&_bg1lxHRSB#aBFknr43^9Zq0BUjKc-f2~MczOro!Q9*7L8N9%A z@&0OP%97iHMPnfB^^K!Rb{i4Hf-iCk97$sB#l(q`HjGr-R@>J;Dr5{iuDuoot+q2G z%XctT2bAQ2C!aGdC;AoonlPw5@XR?aZJ}G?ib4Er;Fw$H=#;80`d}Z05x^5T(ffS24JK{8M{%KcV@qQSd0;xdHyVs6c2(!} z1`lkEuVDnY(&PLl*66nndV58y5-;E?e|LR6ux&?hx|*E3imFdr)*vRhSFF(>Vg`3H zsLi){VH3MY4X~jb}iBUJLY}A zm`r-Cw?Ur_q=DEH@Cq&p6+XN9RV1oPw)bJYM1$=^oV!qu`0@ZDe?S%+=#Q5(qnw+i zxW24L_~T^~BEhc+@W4sgI-Dd#P$r*#`&3LQICsq;(niMUeG%HAyZM%;#i9#ajwOTy z2tBaWlOwT-#WFMrr0i)L<8vR2E<*Th=)%TM`|&_dhwdl>-32u=NOUnx!bnIA&^qGL zJ3#mxU7KlinQn-(N9pnH3K413Gn`hA1*oh-Uk~p0?>}%2lsb5-rPAr8wqiw6y>51- zGX`I`=s-=L8+`nxXm@6S|1X=-!?;Iz~T6JAGpe24{R02 za$ZTXN5B8-HxK{qH^0A+|7)=-%8pOQRaq+#E0KB5bjmy2vCu19#!scO;Q8d7_a@i) z9E-+|9u8p`$cLmm({v=53m({EYl`XPG_EnzVju>5eW}f$_^=(*dbg7G{7+i@)tee3 z`HUKPIW?BuldXNc^xB+=RL4RF_+`G%bt~8OeoT1_X#hs$U(y6W5kz!ybLAg+d*&p5 zyB!>&FPX3#^N<|1HyDIS-~=r1o;9}oetFFO&aPx@&5Oy$5&XH;PgntdwX&nDX;@+4 z&3=BHi_!Dt#4q=aA%bwQaE&KDf%gi6!KQMsOE}Szv?k?5e<1Cd-?+l50sB0d;~Fq0 z%N4F7;y>ZON3%v6vR@_CpaORwCw&-0Q|)Hsv);$f8m?1Ii3CXjZw}w0ev5}OM-%od zUQWzQ3}Eo0ngH`OO!24;Wp6CY_(gfE)w=tp|c;HcHid zd%ZCzQw)=sKINM*in)-IzZux0G_Vz!1;5y|kh{H6DJ|;waho#_ZIC^PAs%F$tvr2o z^y(mU6`qjLuzH)|OB5r0c>NlTsT-9ajRRJ8HxG{z;o$qgsc-G-SXPF6KI$B=v$4Z< zN7KxToU%u$AN@oDKOad9P~3INW&A{k`;kU#^(O*l6XVg_pFvEWC1h&8+&nzoezL-` zpIn_>2zBG$QG%zcU_*}Iu&

8->E#pEuiU=;c$djIRz?|(I+6iS#SV=mBZ_XcKr|O4kTg@qJGSy#Orua!cIY( zC;L52&eV=(Yoj*Ucv#+s07=I=9V=_K$qG}O{6MH|(~hczh&{GR5M($g>d_PTW9fXh~nH)Y&hnUG(*FMs0lgF-o5KdlB@r~_RVd>0bmh}PEVc&%3opwoiYQQ3pBv3X;wWL`1y7-!P)070 zCt1Mnz)Y6Xq(!YJ?9yubs@3Wzym)x0=hR6{Mt57>Ym;|txMa?{&w6{04m((1-hPQG z)iJd7ObZ$$9T7!b`xF8`>SEiPmZZ&Xq9(9)jccDhAHC2dFSlC7&Tx3SJ-K`%YS&7P zUlh7s$GhDx5Jvg0WHQ9Vl?|UYh>u5)y4{V;ST&e1+UpKKZZrM!E8mjPBMab9;W&)RGXs}o1mx#Y?ljj*U*X>JsnMt zr?}z?AqSd%5-QR}JQG=$sU+EE#wghody(3tB^nzGli$@=yMwD=UhQ_XTnU>pad7tw zi`b#&WY)wXqHwpBc8qtKCCRi%aFpHn59&4}QqFFX$Cz}OGir7=#8g<~+y?w{6Ll3H zCtZc8wz9h?bX$NJJqcqB%Yb zn=FYW-i${wfGCKBOp?8MNJ3zw-3aU>DGg0wp$q~PLL>yF3de zO51nvMh)#EV~0IFaVC*CoSu;bSLBHJuzS5xhlLGDOLA0eT8iqf!=p4!@d^)Rm~^^f zNKpadFauD|WWGaTsy3Go{ys!pnud(VJtDJ(({I#S)B=REHesjM1r_u8?49I7LN{MIcN)FD9>ey+?fJcHavF#n4Cc1P-^HDWs=wq_79=CIvYT2HHMLjWGMm z(#1sy2+^jPohh$Tr@W&pB|TB47?w77Br#E03?uf5aWQ@cSB6cFv{hi43;f9~^y4PD z@5J5E2xfOo;h&yD8MfzmlmiTrIvASVX3%wulL}n4nxv8pN3LRm? z@K*tbn^`FE2PVTJbjleiOib7e?rn?E<46$xb?jyqa$3(Kg~DgCkJ1l zQZT6)5WKqRC@HRm*ec5swNNox5MxY@ryk+{mZazeC02~a3JG))8360DBSU9zk{h~BQ!P-F)i!A>%}WuHW+!1$Lj_D~ z?Y?Dmd4)e|N5R)gDm(1qnc3|H+-tCEsbs*t z{~dN4A7LFHm&|^<>B6s-I~g@iqqc${uDIA(tHtBdNq3%LAJRK8H^Lm|dt)lbS3 zskjh2i$zX{csiH#C4T^1hwRamQ}L(0(Gd(s(}AW9xKDV1h?+;Q@J-eOJZwwILTZ0L z=^s%lFn=OMVoM1u?i|zn2|txiaVEaTiAzq#ea@wVXKb1KnhQE^3kLSe=rMGv_Gof8 zd?W1e>3vP7QZkHir^DckN(fqCsb!UGR26OjzBWq>HKB#VsMjxt@bem1m)s)x)yFUD z43VjGxFkf%VBTQv5YLKf#4%3OBfJ@w6mN#rNN_1OVQTW78U9c(J2R}qB{9SI1S&DZAE_A;!o>`$0#D4a!F|YN zh#CGwmA5y;I=B-vtjZJim%SEl!^hI9#0=}`f&FDTHp5y+VutUD&?IJ9IhmMYCh#%&>B(V1|Q!Dw$!W zreuacm3EfRu+UR7!=H-IDVbrV|GWSC{oj1|58wT_@Bi-m|MdNT;Vv=rg+_|T{qCQ? z`{Q^2^4-6F_y2zPPv8IT_kW{sE1NEqCp>@TT8gHq!a`Y{sQn+MoY+p%Ln2>dD@zvd zyZ?zI|5Yn9^LAI5RM)(H_m9Bw-@p4uV$t{i$9Mk(oPYf8pBnh{4~_5sZ?NsZfB$z( zRL0_MnCmRi{{{yC?RS4{eD}{>99aJeO(PorN8|hdj336Ag(gr{*LVMhj`$n=_xGUi zAHM%vA|^Jr!VumOK|tAm6EB2it&2VI&4sg`3+1 z@bCT`3Zqb|e@fOB<4!adih_Zt;P0ph&=ANO5OGIyeW}6yRP-S#`j_wjPtI4Hdq*To zl$`WRf%+q4|#vGlH`S7V_aFZ3J+0?F9aiMy$eU$zc#>jPFz$7hz)mW+1z z?H(@KYkgY}9_ut3ti;uj<94Y~x0JGplcpf2`In6kTF?Ko@#0tOtx9cnPAEip^wl^fbZIc|wtGy*i$r zi!fF$WxA5Z2#OuRq0zJ+!`rowwKSi4u1o)fK%IqFvqI*jl0{3fQA;p}8zyLhFuMOi zpQUH;7-|Y{(xcz37|SP(gIGS$O^@EY8lPJks57bC8rY`R#A+>h<}0+sfSn65AGDfm zpI9+jF;qqdLITl@ECzImYMdMRR$1j5fx7Aq?Mno}ajnzItQANQY;CLE2Gc%PkVoUe zxxs}N>R=-*IyNvZDM5uH_Po<>yx?KhX_p5YVd*j|ZItqYl3rxc%cyV_%FrY5F!JaJ z4Hl?<%xh&AQ;QO@F*z(!&~L7I^PHA_c|FR;E9J>>#pv_|$zY2}LWiiL>n6a)6A_6d zizLhBqqw!N1o(X81*~m{uIx~K`J5^25SHh6UWBgWE65-&x~@{>xn=FLKLQr@!byAXmz5w3EWjB8k7lmCW36R z!K%}ys^H(|%zh}U1JIeuXzyi@ceJMh8>$brRrOu7zc&`GjH&sZP)<8!I)nTWH2&e7MjW%l7Q6Y9%BVp_XvBN6|<% z`}e4-RfwBbGJ|!Sv7{4-cMhOIKMhNGqsg9u6mXLW9uh7bPBE&2Z-s(&U4ZLSy8E zl8k8nO9_t$)32B?`b!Ca1_7m|8U3K7ZHOeUq8W3Q@dsrcjBw2sufc^+N`8cWA1q(; z!H^IXLVG}eDdF?sP^3aWDCv-{O`;~yUrHE``w#E;L;pnkCqmN!UI}kJYZ;y2eig`z|a^EXpQlJp)nqCR~zF2 zcXN%gR(rr~s~=`CKFneKJOlW74sb66xR(R`vkc&$G2rR6{{VwqM=%eCrdSzEyS2rd zvj9jvQv|%91EiiQ0&eF3sb`9SJ2^n=nIa%v>HoZWGQ<H?s& zx`2^eT>z9;7XZ1{1wd)_Xx`uJ6?$egzqQwkjS(>`05XjQK%ub!$TSuJg~kFP(^vo` z8e!1B7`(z2dE(HD8Pk+tAz;NcDe5a2F#{uN)z}$W&{htNXf1#RUFE=t(gIk}RInjd zsaaT<5UVs8SO;dbIj}&R12ftjSfI^;8EpGErmZQz|wgdBOyVwM%_fivJFAU6@;J`u?9GEr1frTbGFl&MX3r%of&PEw+-b5K~ z)>4Np*HUMn44XGkhRsz;07j55Q|`7+=LX&n<4|_CZu5A5E&RZ zAqDeh$iSQx(xhw?oDDKCZ-NXPH$lvRfqA=PVBCZh%v%)$<0hnF-liBBvsd)GRq8E@ zfiZ0E$sQAPKPG zX-^6Ke7p9=-jkjEFAgmP^l*Rw;Ug43J3gOWouI_lUpza&@4+{CqJVxr-oh)M`2Bju z=)&?RhlQ@LMmXP~flVBwIhp7?PV%i8^r8;l3K(|ZfAjU%U*E!i;eYRkl|DJ^dWMsx zErV~r|NQGWFWzrkovmZ5>Q%RO-0i;q_4|APT`FtcZr}d;ln!9%?@#diC@v)?hiTt` z@9X#5uXuq+R(Y%rzK@AFCQyhgf1|20--w}bY-XQ>e&eOow}mc|qRThvAC1Y{y;AX~wFGfacloXassSCFFiWn5wF(|0YRYSwmJr@Z zgvePMhcMmm-%ThA>JqfGTA|ZyL6JzWY!QWgvFc+jc#{HJa-m^bD7Gst71Pu9YB41+ zEmNjmmk_D_xl0ja?=l2MqVv5wnvmd7KTrI{4ap@%AJG3Ad_iL4Rzpevnyx2086uE} zmoC4-ixoU(@R-55#NJ;|reDz|iPSE)3d_IKVFn>6HIO?NLq#*X`WFMEkV53M64LaJ zXhPLFp`vZNz7Jw_1dVAUcJh^8heew+`fcnZ=Ma6|9pUlP1__8lxbce-b-SSu?SyFn zBb}DRE%kDUSKB?2^LZ$4WGJ!mlHQJ1ZVlztzvP6y-B77T>3Dr0#?WDzhAvs+VS9B+ zOA-8%$lcp*U4|g))^Qa7(d4W}f8&w-x?Mze#l{ug9cv>{NoaYVbqF}BUdZag-j|*; zoJ)}l_!9J3$DHpBPX|~nF$#@YViS+lglcM)I8l!VVd6HH5G!Xm)3BDfkzoMN^=aj3 zy6XlLiA)jHUDUg3R=Sj~i+l0OIkc*|z4_&rZ~)G5e<{D5GQ&f5+nC3XV3qOMByPyn z`SLlQUT*668Fx4AX*zqV?#F$g4%gll3>m}#d#JDuMVDN#G=ibb9lD6c@iW860O{f6 z8pcwG(~XI0R^*S$J{T{*@xBrn9WY;3cwg~PO(nV7AO#|+pUJI3`U`gmkvR&v`fjG> z-e!Hmid=EZA5V0?albP&=-ANF!r2Cj9p!&x^JIOH|24^SNhDjcbhw-zE>@6oPRo!_ zG7bfX3-#%OO0x&_i|@rWN=V6ti!y25RX|Hmnz5J=Y$7H|z`@))V#1KVg&nFnPj~cC z!dGlTNa}_!Ys+z1@eNrdHw!KtCH+E&Zv%XRxdCII(3}SYZzd4y@+GZvs%|MQTFVt) zyW<(=2tB$tGPrlhwNt%L@-j4FZ>Ounm(SCnmYS%DX1Wd?VSnR-Yo0(1otd&GzTwNA zf{pD{I>}1;icBj(%@#0j>f)xO*B(S>7W&O$%y11uBkx7Vo^$!ya~EN%kn1@IZ#~CD z&a(?U0qhF%*`{qQRse^6*=Pcr9U|xRS5U8gjv5wqxkIT9pe`@>l_Rv>$B#M=D!R&{ zFLaednC~i`z{sB<*5Ar+eLfHxAo)#7)t(b%#eW8MjU5{T6t;J27hw&0&Sw<`iVi+{ z@6aJws_KAApB_vfCZy|THN)N}3|cFFve4_uJVSV&hrCf3*};kOM4=&;T`inZ6R8C_ z1bN$jW@`z#T?4av!-f@Roc%(V=#F$~e%aCT+)9#`=Dr|@LW3-=^%v5dnzVm`Z3vUs zv*mmdM+6T#hMJf~Br;(~MuT@B#GNSYhZ=lGgFnj#DrA8%%VPZ75GFG;MmFmRx{j?h zb+8{yxD9YIZ~3rPxu>~AZ?Zido?tiO zXmB3Q6hAMXWbyPXNr6%!h8A(e_)cIvU+La4)^|ki!~ELP_eJ$o(Z=t;Cj|U|?7i!D zTSt;L_}@8A3^Rbx(g>My*9tl*}pJTBNJyYn2ukMM4z2AVLF>w$+;I$C%$U zZ!pg=k1|g(d&ea+@|-*Xc+sV*UELOO@)8*t85tQF85z0G0*9Hd0Jom*{&DhdpnnoU z-|P2S|7ef&_uAb64N7Jy`B^)gJ(Dx5RLhP2I(cW0=Lyy zDF+oMkJxg#(RMq!|4@<;i5fw?6sKC*9TUVQGS~^KY2t0OnQj2NN0rVLi&AVWvEFjP zh229Q4lu-w7ZZhsVUo~)bhveNG+8XbQMmnshi%XK>)GT4o}aMeZSCz)t8nUEqB6*> zTWnp2b>bYLahxtVxNG5N+!_Z{n_74%MV*D9Tc=L&Vysonl8232VroU$wFm_wjNwUH z$|fM0WgTqGB6X7#xWUl@g%st)ZfWuH4Df~v*Kvb~yok(rXD3fYlv8jOuD$RNn9I}W z!eh82Nmg@?@1k3UqfNRtP|56^C1Yn1@MwonQWL53smb|n?q9XYx24{eDjmxvpP+`x z*Ij09{8behij4K~sZ95^816?BT4J=1A$WlWzNxx#C z(p*ndnKG8hr&;hqghy4s+27xD{xDWd9M@*6DnAv-RMrG=O=u+T8?d)E0!EEFSSK*>uvxZ^SIi-G;w?H#=9 zJ;$}vtqWM$N4))E)jgo>c(xdwAnL@~PlH?gkDlJzz(LMm{=y~U_G`3z8@qW~AM~Id znWOVx&K-zFwr(Hr{tgGcw*^4&4`RzAwnH?ZVnJ_wfrgQAgm6jzu%dVE)tFD#!#)a> zXKyEG`N5U`{AG@_e2n{=<5!zld+{{;Zx5$a7=~VL!i`|T<5%Acr`MTK9i#Hk5h>pR^E7K z^2_Ps+)a4YvUVgPKfsYr?zLoAV(|2gA0m>Q@D4rVQpy`RxEndqV?(L)r5q5A!F36t z_Vj>`B)yV7qWnrx+acx3pu3W7Ti8e;Ijk3qUUg8c@rG`)F) z4d~eZjz+h@>7?|ye=D4eX7o{_e@kWC!44SSK^XwD%Bnp<8e559EJ*WLb!>A6#`e0D z5@2KuI2ac**+Iga!zlQNYfIK?Ek?c9|7IW|kYIM#{a0SdsYDWwScH1nZ z$PT^u=HXXJnDSOx_|BW~^#++3`F~3=YvNJEw1JsijH~ca<6sdSMbeD_0$-WG^!vkf z5|FWBK1`}It-IN3J=Wq_4ET>S)G%8r(o*)mnteSE6Oh7O@{1IYExVfxOZb8Gcqx+= zCkFd)!orJhVx9IAgUE%cO&{s_w}pq$nbVNfc1m)^ip}2zAk6di2c*$RIIsyXlP`v9 zmLHmdeE27tzwoc{ot$qLG?i{3*1vRKMdM8XzCItIwq;?Kxlja}LuhXte#Z(S85NdGLacZ2k5pN>Vs%;PIC0o66tJ-aC=p-A8p;xx zL4-A&PuncgTHn@{O&qxu5(x|Ja$3=mrDN3)#s~v-Sys5zDT9d zftBQ-eEAxy{L-a&IU;MTU97NN>q;w4g56fXRxt6}%Nu*zUgAQ-CaoF_C<>&P>N&g8 zx570l-$zV+V=~1a!ZUTr#8OeXxOt`8M#b^r;rYYa*~!-Q zymnsjKCch($v~VOIavTpDUIQ?JjN5p{)JoX`Rj11d6-bSrb(H@^C4WR@Cx=h)Kown zJ$!OLd-$-xAgn}aM#_gUB&e#2X=#Cts_-kg5G>?nm<1@0_byF!Q?cB$4znM?;~nyf9p}Acg#XG`u=@pHRM?#7jF4r_in%H z;dj4x`;GlQf?=a~`>p(YW@$g+@96HU{;k9t!xmGANuZ<>@vXxnLG4_m*2tP<3=M1)BztIHFu^)V|!Sz=6 zWm>jiP^<}WCW*$VM!DFcn3JWVA#XEdtg|FN#5?c5f7QGF#QtveWDUroGHuZ=mPFD2nhLg_AxdnG6X0$FW7H_7#+Yf}tz*&Gd!l{EFTj+4RUgcA{iI>?- z6*1utAz8M|&@3}Mo4&)-ZQ{PY=%LD=aZyLCEP6`XJ6!Z&@9cRai!XaWE+#$N8o9Yg zz;AV9r{C0!!7y@3fIWK6`j~Jws`M3CWtN(N*^*wF%+3DkP1_L>Rl^}2|JwE=7uv8P zC1A;F#X8aZv^17jc2o++c9arxIEZ3@L-{0EB1F__mO%aKXbn;!hE_Jn{x%ogW=IlD#uQofC3jVd@ODXa9xJskRhL{DD+$K(i#$e$J5 zXb!9Vsn&sL=b}?;g*lvy&Nqd~aei11Z=~CCQ}s zr|#Yzo4SszPV9N`_Qi|OS4#n}-RwE)=mO63B1(vC}Sm_yqBF{P~Twj~r+T#uX(AAb&zp=ZLY4NmTXm2LFa zW@Sz&aKP65WU-p>tst81AH6y5{f@d@{}TMISj&VlP?#UXp3t_!;3kKz$EF>uCu_H_ zAB`!X^X7Lid;NaodR)P4h*9TyhXancpDmV8>FcpAulrTlHtaUhuiAPU4hXopFD!r$ zP<=ALl#%nkE*J;jZ*Bi^=kYySera6Vj3(d|q8cmmwc1P@PIdHPygA!dP<0yPN+uE}Ka**vBxVs{|*TMT#&wpT49VJgH$YGG5d*%z>g;cbB zFR_6~;IQdd=WHL5_4eWzFsa)ZrWOvmbvO5KkrhrAT$DL68_m=C>@DmtXD9Ll<9d#~ z@vU1!|9|6N-R?1JrSprXC=!<2kU_T7G{fF4vFgLafD>4US@B`(8d*OB_uZr_Y+j%V zKh@OjueIR&M&DcrhZ(l+{!+k~nOVZ_eeYp%B+3`3y+9=K|@Z z$U>HgIbqDke6pFFo`+$FUt^PLGlSx)hqXK)L;?2OyOV!$SfNv<(#V|I zy4<-R@)x%fV!fEYT7+QW*}FpSZ$lBB;Py7kh_ZtF+h+(5N9!IJ{c@oCFtm4})uItm|{{9csV7`_2C{{06UNY1e{HwW#ta z*;-i#VLYsST4RdR+rAhPy*m_*vH9(Wtx9ms%j|gj$&+XIb{}u;??{l3dR2t17M~R# zf((=a>O1Ou`$FfhwrE$Qin5vpw9Q1PR53XoOFj^J&4OLulQ>z z5t>CQS+_LP7s4VRi;%>IAvuXezKGyX>MCNrau%~iX$zAOx{(E+tSHin!Dhu#--!a1>#ir zrAI;pFHsl-`5>6L8rnWQ({kASbu<7`kYTr^s3lO#l%5vGD>?-3NpSXkZd&z7%#LtQ z7wQ)3dvV;&e+G*L{yUwGzinFm@e(8|N5?U-ND$^ckP0s^Sb~u zv4o=i^IHowbTm1g4iMG7m%qhF=`Z8}Gv}^hI&H4158L$I)N025C5NJn$0r1Zi8OngDScEV#~ySO4xA z10!>(SZ1?uB$6C1yQvA9I*Eo8&E5Q$!N=8O^GI4S{P-X~-wui2yNs*ZzLZ^kZeaBc(8payJXcQKLvX7rlEOxf}@;Qh=QM3ll=*ewfR!?wp@MkEq zbdf@U^}!xA+oRV;r;b8nEyRhSzs4FAhw{M_&`U*?vy?Q~253nV$ylc0sqZN?=RJgG zw_cR_Vip$J(MaFh*+1BRxV!UsU)Xme0fHTJeR?#b`g3DGJT{R(e1l#2@LWtZLwUvS zC!Coh+8AG+d%NJT^V4&9o8U*P<2ueeIcL}7VBe0|Ze}P8F8&q2o@wiF-3iw*2wl{r zS+OM2<~9yY&bOwgKf#VUcA48`ldy`h`+1abj3e+Ma&RyqA)PjRk7qq81<84IiU*93 zN0?g%k_>LpL{73GUS%nyBq3G-@3ZX$m63@UGwP5QLrmpLD=O3D#Nl2)ev7y2m{^p_ zK3HS_k@T2y85R7?s$_{V|0$FAVb*y!R#qXfWKY|AxsVmPAuybE#{Q!O=gcn}{S|&0-C&3TfTE zDhfPhvS*J8DJLp<2{3Fv^3Q}pPSUk+`9fuLGosP8qJBSz0(^A1IB%?VGG~hwHkHKW zr&bh4F)?O^6WHNRE(o0MJ8;S^YI44bA7qi5;1!Exh+J5v>!6ouE_e&LDuo+KB9e|9sn%7II}4Suju1C9W20svsJJqJ&ja3M<_0=IfIi^#2|q{JUPL66!dTCB_rIC zAvN#M<`29~*?XUvtyG_H$UvmGB)a2ZqOQ(&nmv9~P$6acRSw@75Q$t<2zsCwt*Z_w29U22xsJK=NJ3q{jZB0ANJeF2uVfu?JSX~WkO`XOLDX{(qp~D zq(pf<{!Q+->FeJF-J)UB-R6!(JhLUWte;p=P&%9r3-0EuIDLC~irt4mzTC6`{(k^|a%V)hVWNfU z#Aq%`jd{cDN_|oFC&%^aQd!OtRHK1)_f$8Vx(R1HI?bG3V!{D63*6JB9I!%tDyxW= zDXZkw7KC>4vn?)g)HuhkC7Alrbr~cMigrmpBQiSejcK(FDUA^n2}y*)3ecEQNn;V&NiNmHsNUguZ(qbp ze@Lul&miaFX5`pRH3!V!_(DqMk(bewG?%zCtHcyTcs{Vwd30QP#F&Y**c?#86c}om zjV~ULAKzvm$RIvH4-vuv5{fs4;NUo%vvFA+HBO>7zSw^9=+V~Wd)wH$?>sB*UPTsc zU)$;yw~cf0$y`pxakZ{YO{RTvXd~~b9eKF$4f2F}&z=299Hwrq$Ug4^kI1XkCxthY!S;vG|0EnP* zyCHy%IV#JUW$?K-@WmKVf#tW3zv_ z`0HXI>!E9mmW@Mzw>VK}w(7#|$~v%vVsP5yKJrn{4cwm|E?)1QAD&;d+J1z9H+Z8v z>6mw6WR{kH0O!EL7SF((4|e{9nX)(f{&bED`H4$JTnwcIUqKdCCVh=|&ra3j5bv~2 zPG<9WV}y+j8l#ka!0p2L!LuhnK85uRF04g;y5F!shAf|=ndj~&yJWw=fT+YCU0uOw`{SA%Iy6Umgvk9H@A^y$3^l4&C+~ zQugnuvx4wCoy8JnOniVtkNw@p5B3;^YVX|wkDf?QA=TmNnvN58v3eti7RW2Dhgy;4HZWdb3(e}?J zz@LdPy%k&upXWtJk~Lt1=aYv1?|61#F(^Kt&L^)fDvn_Qk3wu5_H`&&E4NAGpfrNg!!(jCdl3&J`6_xxQeR!V0yeN0ytjXCe^&0zGcfkl- zEVV=sSOeE$1CbiTeQPmY^q5lFBkBGn1J%t!s$S`!bouaP&?l|}Rave@$4%v##4kW4 z*J#c_>@ZbpV6PT|Sj)sjM!U^p#7s;D${t$O%`)TvVgEBG$z)ecfEvS_sh6L^-P6~E zg)YL1tIudm8<{PV8aoyyHS=_F$q@1)W{?Dc%#juz%yLn}-(k9iS$g68{MIyK=mB^()iHvHTbO z5O$$FYXBRM%bE&JYCQjq{Q3HBQmFWymOBFDe>&RCl2#Ybp)0%|;dlck#KGO60@--s z4d=xgR&SfYV%i1(-o_=MY+~cZ;)@0I4>7-Rshg(C-W#!1vUEeRC;?dsg$#beh-8@dDHZGHhT~)`@f_nrjl>uVXZ7;*sRceZ3st=0(t5=uCUoF;ab^z?(BG1xgcJ*{< z4D}#?o2s7^lo#?hj{x)lx|44>I$4^H4hybXxKGFKJypv58|w=0)x(KtrS2Q6wtI!R^)r8nmD5p(@ z1U9(pa6%8H)>D^gV9<#m0u*yg2LSd1Nnk#;4f()sg(R2XS;z2-Ts2TX8h}%2S?r{s zuWa&>&lN-~km0NB6^yA+T574thc1!8s}vO$K-rW`@Vt4Jf!k_MS96tM!GcU zMqlN8zOmLeFIi(+YMHBKUpRF=VKZakS-KjU9_@+ChQFJRE)<31CdcOR5Y)}C4eJ^` z!N|B`+l_z>l@GA`rIZc>)=e+6teZa^+7XpCE2MFDN9Kp>UA8h$B!c=F>8FvOdXOe0TohWKPLy9I0uuzMqi)BT?mzR zMVvQLiU1RS!3?|>lDNa;YGDmg$9f;-R#LGW7OOib>DiU>oh5U#6V`ckdpl(%n?-}V zV1QHxB74;sNtdkuiPymUAsMNSE;X(G_-az-CW)DFtmB;9u{Z4F!%EGInIIm zWtw(Ui;bV{Jbm)aF147@RSPmT7z`#qFpgT9GQSZQ^A=;=*#R)FH_B1Ai`T`iA**AT zbTfiGAb}Wn-2Ay5*BooU=&jCBVM8Bj+oBmXwYkC69jLZs-PLl|?m|yLc{C7eOJi%( z+|VFp`v$H@SL>hT2mtW_dT-RAvf!nZDk15Sk<}obKvt)7J!hRbs#Ss-K2Ilesxn0{ zKMY;X2_d#5nw|-gwTW6K(`8Atl01&BnE`H&$)LSPHy-(&NT5GPp}<&v%f4UM^d;;I z$Tkgp*@ZO=;R25H%W($3QEYto4pn|DEZV`NU6@IpJT86eED=s?B1Y&yXOl&JR>u9O zh_-`MWn{SSB(jR8uEHu-2fM8Lht1G9aQ*Ey4Tlnz?@$g`6N>$iIt1iAr87 zC1^29f#mEamaxYcZ(icAS#dVpZjrBERY`&aK&KS*3h;jU750~kWd;c+e**m#-w?I??oH6ggV`fCH2;O}V zr!Wu0C$*vPI?h>72;Ccz59)nP_@txKB7p5JqbEa@^#)%pHV`gXrjg7+`PE?kZoGoB zzPhp4P6x=9-e5xTEH;t?N!$k;<~c@3oUzUp{*3-)mMk(6;zO0SToiMr*bZ|_nmMFg z->AIFd~Mo6#028jkConSvRiC6pH>;JG&baoGWCJ4yIcv$;DC@p1lpK`#8vwl=_fP| zph+T0gMv%=n}VLEC@%6_IU#U%y>E37tIwuyg5Fj82CmyXg8TT{$-w^F!en1#`|HfW zUe~Dvk~^f*LCGN$%1aP1f_lRZDcGPorYjJ)it-0?7$L6cXN+HC|3Ss3W;DAh&pBL~ zX#q!1FBr-b>*VrWZIk--GHkk4!C)Q7Ly8=XO{&DB8rMA?E5 z%$wAV9FQ{+=Z@ZVPDA^=S<}IUC1Ja4j19!KR(yabs zTmZ;NBb)o*4BorTIRhjdnlToqw3|>7bcfk!73^n%GOZCp+9oU>(OwRo9_v&`7AzxZ z_ACXnR~FLy+&J`rJp7bYnwbG16EkTHc44Pg^HU3%~?oAI296d`iZVheqkm>mlT zT7Nd1b$E*vcqI+5NCmb!U5ixWQEvY@VH0*M8CJFUV$UWZ#ZzJX$=1W&eeB3(2P{`b@Fy%UUd29$rF1f6cZ9uQ?asR^{ZwDhb}?UZHWnDC?HUD zI8*k@N~Kt-xlu&C@-XJ3J94hH=fg%IwP8z0jeJ|*0N3S-XJg#+YId_b483l=2u=-^ zUQ*KYDXeK#EH`>|IR7hA@4QQt`_*Sz(M);|5o&>3OuV~x)_Zh_Eq#+bmLSmQCwMu@ zYb?QqO)27w21y!@lzo;oJ2v+4&9-Cz6rS|30kezDT0{D|a3gttm`AOFrL~@DE=?6z zxtJo=+jwE)LI=4kpl3A|&C8+(8?hSr8iO&UQJV1o8Fy3b=aJn?}~*R?#fcVJc-_W?ZBJ7#ucttl z6M0s*_ZCn6aOPYIQEj+|XVeD6iq)yCuzgViMTJ(S92;pSGbL|bMEJ`*7af3^FFK(< z#+uBshzrC&W)~5lY8y|`)K-pdYD7F2thYcLx#R6BrPKjZc`t5VfYQm~=y@)ChXF&64KhzWeHzQKs%-425mJ6odwR6#tlXgVe9#|qrrFpbe4@S@aoibr@(4(Bg% z>E4HC9kQ`@bxX9n$j1VS$TAwc3j~i3CmJ2qc}P<3qu`pB4rMwjrQ5TSaiyLgtaR+5 zb{BebQfV8{&@asNBJ07A=$@El)c=>MS~-7QK>nfe%sqme5Gm_Jw4|y=&+QLEV!OCG1#{`S~6l~11v>+n$X#20*|aw*kkdKrXeb4 z&YPmI+ACQZ!K5mg(~n7nOEZ!w?$Oi9<^JcxNxnt+s4V3JnD~jKnIsQ=EK&|XkQ4aD zE4z&3sAnZ!?08tmSub84+Mk^tPVs~T_~GS+7@@{2LS}1cB~-N8dbLU(>WQkxEl0b( zc9Kn%5`G1)9gu4dX;rD%oD2of#*|AWEGAz=B5P~SiL!|&aliWNmLVXxIJ62%i^y?b{n+7CUkYBSgQ7G9;z*N9j_TnV zfMgmwfR*CrD|pCyb(1H=XnmQYrq;q3`dLYoEx50;gE%~#PkIP*^;bN20{bo$W_-OJ z_4X#*BO&fOOX;I0%=2ZQ#s=|gq}z?uV!1EB{Ia>=S5`88Ie3u+e#&$vGT89?$?M>J zcsP8yVH7`qe)s~H*XYapWRCa93R-@5{`_TkF2;?+omET`QtDR1UdTulp2xODx!iy& zay3WiPpyW?06zH%@jp)|KX)t`pX(^u>P>aAFMj3hj4?uu)+#vGpQgrAMx4l1GW|_$ z&C3H?H*J}S=+ETXp};`j+jpn47+#hXo{)S?}lthOotD zK6wk?=nwDy{!Y2SU{#kHl>s)fuC$PUv1hiH7k?Q4tEwT@TUds~>dlG!p=*W0^r1B1?T+ZlSmMdu_#Esy0QjJW?NtwEZfyn@V(p%Ya(D7^0JFUfs>uOD+w69dOq}0vyz!%Xa zJy0UZnAFy%QWp@^v_`e?Cqq4*8ApDJD@kWRoJ5@!KSiH(m#~J7C=5uW&W+9lC(=qj zawJyUBr2GDr4NWY&#%f`Mohs4B2EC0&@k7K+b~%i;Xrg7QIHoFRojyMOJmk*X*wNS zIt7DVL2_?GEpdPGW*;vh;AJg~>1|sR%`N1WCecgx$;J8j&Z_%O?1MO=7{sABtUd@b z57HKz5>QgIc+=IBBT~P|bdVY#nFt9>t_&a=f=-#PWjuE)hECmV^`Y58bK-I3yQ0=aWZ=N3T!Mw5wW8ES55L zFbC2C(oEpR6WlmGc>3(gHg4QW7t+~OvvAVLlTIs`gPu_egyypFNC<@73K7=3+tzha z%PhRIeYwOXws?*tb@fD)wAD}WAT}^61vCYR)w^d$(~IMYzB*u$*(Fo=@2EiX`Kh)^ zK{@S?={g@JoS+=xbBETb>O!iB7udL_q2joaSRkbn1(a(P8aKWnGtqt{QTa~rR&4fX z9Ptp3-e#QdmekC+2n9$@rJzhn9=PJVCs|pHv8J8$x~3xMm~F zsM1shqU`@i9c39yZLvYhHAbq7{;>z7QDkw_O@`#|Mw8My^2j%d2RA8vG=yvplB1+k zXXJ2Bo=5Gq-`<_udi!PX&y(KIr!cgGL&TPjyES-4xpxj3HtNS&X>9W2nX(Zt7FUK! z7)^)T7soi3p|4dYVfBh^>AG4O$tuWvfa!ljbNGXB#)`qvaZti+-k)A9URziIXYcGE zls%lJI9X=sJ(rYxKvh5mJ7Nu?Y}DY+SpZ5c2Qly;l8X0zdBICX({3=|$+a(GA#<%v zx~lTmCLS@%Q! z=Q|U;GfnB*_+GM_#c6+OXR9+`r8i(Ep)cdn5?x!silj!SzSP{K16(X?XfCswPl=d5t*cI~Eb8YDk| zahwx$b^x$d5~t!q^z;>&VGWAXZ{5c$tGnx=w+0njMY<+eK~C9G30BP+Q5Mq;;50lN z+NL|ThSo$&Xn90kF{g%mL!qk#9#!|l3VLyGeoSM z{@OV|G)n_&TL8WKApDhw*s)E8J9$BQ#)ddCR2p*QsbZ7kK?c}dkpUJIa-g>>CJON) z#TI$$5}G6d3Y|;%8{YB!Q+jZvy5*rU3y!_Cp{*KN>N|7UOip%;@7r3&qQOSN&-`+1 zuJ&K!eqWeOy6fXZ@FElVqZu};RCESkloIL|J0%XPo9O~?k5qUo7E^W3`^y=f%+jYi}o|!l`_*>%85i50B$+AxFZjp+KM=E>} zL)x|HUdi5VosP{4!p`D|UXjvDj!_~&9g>k)Pd*V8hv+k4KJ0NKS3&BEi(E{LoEAyd z;5HxF8=lYC;1kgpGRKvJp8Oa1e5j4JqD~onetvkQ9FQcElnFXtSxjCbl_G-#kZh^! zp-7YnbWB=dBNeG6aD<`g1+#0yjU`>YyW~YVCTkfWTQx2Z1GLTU+1qzXe8?<=7GhvH zL+j^!UO?9p%;4n;#u5%%(sjwgR($r;q}mUgep-UlOPrEtQxPgY-BzK7F4{@SfPd#c z`Qz8fk7T8UPWpobuSPVz#H!XxdA!Ksk&O)RtpLIkNg(R9t6#ZPZZBIO-EC5MRb+M& zY}uwG{Qx&I=R=}LHE>ej!{Fss_P}c8o1-qb&m`foKnmc7DY5Xe$Yyps^F+*6g zw-yP?e<WRr%b}I@~Ax3II^z=|NoUK7#vh4ktM$O9npD49zBCG=;v!yUC zI?H`s>twRQ%rbNUZ^c*QnOhN}Cuo@tnNtDQ^H;)+Tw2PyOCTqmS-JyVwOm?0m%=#I zs<_!UE(~%k(`1LtxpZAf+L<^sxLQW4DOYMhMW09^4bm<{()2y?KWSI@S{C1-+ zOJ}GbtJC4TS?wzeClVLh+@KJm+#Diwu0}D6HDm&l0%)0RC*2)|gmCvbq{;<4VI;(M z%+JH$+j+S4KX>jm11ZT>9cw^lc8WYfkRF9XS`BWj$rscMWaT1+5TZ&;p$5&%q?@Gh z?MDt0ZqLK$pXtI;WXaW7vo=eM3LPmD`o8St$s1^$vArA(BE4R7nCXnAXxzTU>Kh~} zM~E#oNrM0qkG3A)JE#wAgTG{$38k;b0;hGRph^`pK}m8R)Ac&1BdNaE*F={!uwB=wqS-XW&3)^eTBwOi3hiMp^Rg2HP32&oZ zX(04H72<1G*cK*eTYYLE(%_sS4@B4FeQ_AQxgPLQjNXlP119D*dtoO9oD9p?5HM8RP%;i7z9g{Za3pD6&%Ddt?xsFfJ}w*KGtPvz3v)&SXl?3O^?K^r#%neGRJ6u|K!SxJDPRYuQwM~?`&)eq?FnGObU* z2Jf?O&t`Lkd_)b4E9MnB-DrzLl)zjBc#BAE)KnU(O|Z-o(_opDf zd5QT@=+El@1N)F>q0Kl*UM_57m7AvN74K$!l{%?sl~Je?LRv8I_D$FrkmI} zKZIplt9V$xm0oH^<4U97e#f6cg_Ufk?Hix5yd`0jOoA-;)`z{wg+*>NGe>zZUE7}= zFB3owZ8GXjSrVgvgo z5-uPcMN}ABqTP^w9;&@?)Eo6G?epral|l?pcAi!SZJR5HWlv3 z^mDo)p(Yc^weGw}{^KKs9Cv14{n~M;ewyCgqlaP5KPFyU`#Z$<| z{%xHdg=9O*>iH1I zm}q0Hsz=e95QvmW^F87s61hzySAwhb^T}F|os5gG4>X!`Z(D;aPmYJqYLi88@p^WF zu<$P@y;snIrluqPVpL-)*3#d(7^la&1r~}zD?nqg&SmPg{ z3&JxU=%gSCYhjZ>hSGve0@Hzu?SEGwW2G>g1N?5v59XH=$4ae> zPX^k=Cj;uhCq)5Rfl(^AE>5We05`Epc~$W#JmiLf6=->}b+PjD6{I|?8z%+r2XhQM7rmU2)kw=QK;g$ij>UX?anRv4{7PpKN!J+lx-wM7$R z!u3*F1^mM%iu!ewiL#)0k_c4)h#XfL03hWhOLPl}m5TO=va?G0hO%9WmUSI+0Kr9c zC4&~jE79=N_j>BDuM2E0neSzWG~gG@saay@>62&svV&3|t6W}oiEK9O#5pqr!6qEJ z7?yK&P<)wRYoj_#55TFPMEkx!FHSZdA$+TWl2}$77hL$y08vR8LSUrtMnAkb`s`wwz@kPNbFmEssS zv?}*tNy)_+sSae@2R`JQA#2ZiECJ^o{gMy6DJ;KHlU;3#9piadGHTC;v$gNMA6Zx= zW=t3>l9%mB0^QvaAED5>w3nh@T9B%^M-ZMkUz?p58a?XO#WX*0o;bDT@tS;B3p@@E z3@0+qf{fHjdSBkVNq^f|$bv`R9N`V{Lg8#gg@aYnwD(i{+ANT2ihwEt*ufebrK`*%s@jgv!SzStp%@ z4_`^FzU9KZCYTr0c$JBp^1yTxDD#MScm!jKOgRE^twqB_KHs0|Eq7J$%rukM%517Z zHTx)(9C2qDWmx)?ij*21n>kqF4b0{Nq7WGGNhvimSxUWjH{7f^)CdL5Ycj_S5qI$d;!cf)59+ML7sG1ib8yAH424wZbWmg5#4s z9g(k!7oGaDi{0#XJtSoWr##`Axt;%|itOyjHA=|S?XVeeK=+0!x}TsKA_ zHWg!CQV>BZ+DVeA)?GLamxL1b>+08&HTzZT6Bu0=N4en8*vsmrhdZrXLRv$P$MP;# zIFs{cKD&4ejarP^eiJe7kflHjfkderKte26ZY7#zMyAyFje3kK6YmTuJlQwJrifI? zLVxv3dm4U!JqUOF7aQ>N`c0_E@Qsa2fMz5LASzS5)q120k;U`ZG3L8;l}Bl^BbTqg zdxFa$VKTm?&awBoZzZiYJXuZymEsFRae$K?^986F7b9_HWOQyrT0+}cTw`iiq+D=# z`POYkM5I3ZDdH{`>phm2WG2DUkRwl+C;iH|beuZInt{l=6Y(~z;qvoq|J16EqcBerq%J@`S4dPL1GHe-sXRGz zsDTbc$&w8!?YE{cL66NHX%vAfn@GpF{;ZZHCO^;$(~}EI3ZukKiWh0pOZ9ioP7Y7# zaVvq8W*27?%l+txzZ41N=#Ns)P>|9d!bs0BQy=%<8YK{7Rhn4EADJ+2q3!JS$D>O= zBu2z>F#^H*!gGM0nNGjFbnj?|h(rE#0#}UlcgqL8S?Gp_9p?*THo%ycy`weZVm?9d ztmXFTRYmbGn2Phz?tzHCi{F`>MXH8u&g$_w{+W+X?CxsOR;_$xT0y#WwVUic`h>CQ z6fDah5bOQE%^;@9k}4-~GCcrQQj)^YGRl^!3a2_*W0nk5fF|B3{oVL(wtF=D6)qN~ z3q=haFi2>g(~csREwT%;==YBJIOlBI&(~1WI@2IF)?~UM*0OH~2MMudUNYW;Je3i0 zg_<6RU5z#DQyWEWvBP=%3{&wu%->QGT0Ai25rM6|-H;v* zAZotUkaa3gs(in-x1(pdL8Fqnq|ElG86Jr_olfLcg1w#ngIqxi;EMYK&(Xrr)>%X{ zgmojd$SQ5tkUn6cW8J|YC+~(_#)tBLB~3!;_VACB$=l)H>)FrDrpuJ|AT}bZIP>4L z{x|~}M^LmD5uyPbN1$UuhU5k-vcVXtMY>e>|K{2wFNDd|K_Q3}r;rb{z8;X&2@)^K zX3ZNZ8D8>flabQFGlUM^ZLy*PRQ)BiH6k7qZ<4Hqu93vTS z(;*^X$VQ=3a5e4N943f};|c(dE{9X-D?*$lHT;AKq%6y8h_2OszQ{4iKiklpF^9yU zK?5dnLCp30!~A;*tXLy42s_;|IvCmvcc7SrPLdkJ9u?g1! zrr)cnB6^kVOd1#0zrvG40n$bd)aw2v)VNj3Xz6bX?ll-m3p1{aju~utZ@>rrukfbx zs~#gGbb!7h&KwiLHF{>akYOKiEqe{gkFNjQ{hh5iC-W?*QL|)M)Q!`oR$)Ez;~BZh z9A>@wz=N z2}u`j$yNhkc^DHiM3?D<86BKQrj{}x?7Ms;$}f@6M#VunpKif`i@4*1uNE8pd%MRo z?283|-_m}E^;AIdn{ybEee?JH@NB^|JMrbP*wi6^`(i$aDk^7b*tF=v(^@)+s1e)n z6}{t%6Iwy|Nt~&Ynkf5F*~+?h_7Kk#!9=E`zz;204gz$|d@>Cc1qVaYBW%btt{SiA zS4*U$BRwx7lZj%cM$S@K)uj-pJ=C|jbn}R!^@g=nXMDURugj|vDNo7k&mOc_v*r=7 zZB#aKf#sN|ztFYgc@~M!O^g!7mGFVo^e=ARcrsNcN+b$Z&EMf<-PJkua7DLd!zbVs z4beTZkp_V!f$Sy9Y5D?`EJmZJY)+M3gy_&;2w#3{PqZTFScTm$_k6ul2^Ch4I!?dlEv zEPcZrdp#NxW7~QabcxBZN=eT2aVMVM8+J1t;AK#<01vpI78#X9p-4R}0nC8~A7_Vn z<_Zfn_6q=1nv*Wm3Ao45`gDMBd0P*5p0zDdo&zfs76diBQl!BqRc!9OoEVn8q_2Qn zlPZQNyck*7p}gAIf9M7VUswv$E5E#3{kHS*S@DE+Yb5KxW197w_{6d2#&=siZB>ozu-ifl1K_q&Sc7-z zU-uL49()Uzk)=v%;)&Urc+3haLfxhZqiDuTM{HX;;p@z0fUIQ0-A;hO%UyZee&3; zT+ZEAc54DdDT6T(UUt-&IGO{KADl_+L=_S?IWdEe>vXZKC7CJ-(&O(6@U z_ph`VpM zK;&dQ`zb%d3xaY62d!xsl6gKSij?ie(k(x)phvEccK7xmaz*Tg+7T#(sf)i*0`Ln6 z2QemPS&n!_)C~AAEs0JjDu>=pg20rka^a=s8c0{(jw-&C8MrPK$(NbT82X|vdJ_kG zdwbR3+Up(rGuxXC_Wt*a!{a$z%l%!Cdy`jKBqtjF-uZV>dD8^@1(~+n+u;Z|z88|O zn;LAL*Y@GBqimimE?y4$|L^|;+21>!z@-Z@JYYnt7en0ijju-h7sbE2+dIaEgs;9q zab>+{hsUS0f4i7mOvc`^QBLDVD1Rx|#*EJ+>9V*m#;{? z6S1H$vC9{eV>Icrfhwx<&7*yKXzOo|q#U8R)3(E623(utC-?8!!P#@r1X%%p^vNFQ zO^x$MFzK2GKL|v5G8bsDV2N2#n=KJt;YK;Gr-3we;<^9QOHcl_$m!#|>SI&Os-%65 z;%1NFUwVFfDGLk$QhE&BthH5O>jk0zI?vW8VwGMo*303R(*nuEWEL((w#{HzT=v`7w! z17a~8cjX{hF(VD512Y{F;YAlG&wt}Z@j-&o5nPb%{RYJHOhrqLA=%T)bKBg{@-%9z zz)`lkzqKuZ4WFV5_>erF*?BpCMaiS(^;I?D?Z{(`Wr_QzJke7h@Wtj|&VX_;l8zxd zH|!&OL$1G(bS*sx#~?OH&Jqa2oOsY}H(TF4IJmdF_jGF?vF>UnoU#C%pdGdrq0rRnNetvqco;R8b?N0#si&NCbbmGrk95c>L(|b0230H)!4*uAKc3!#(mcUEE_8Lf{4k(M_^X_OBLCW*&U~V#|ju*XA(I z)iHZ^>#Ooo`h^u2O+Z3&YO>gZ3-xo^dK2()HGu%-EL`BF-2?^UwnpcWWq`$$>Zh-y z4^>|Xm+qmq&UYM)^$5airGpKNiy*6Bz>Z~&O6(l}GtthR)v~>*ZztiHrl-p~YM`cH zyEwvQP^xfif|8*~a$QXn0Q=yU$p>?Hv3u{~j=Z~y`k zlV~8rQ)>6pFbl{wCL=B$iNB#r)A=z!AQxltK&4vv`IW1cXTXL-O({4EeHH)D25V4=MiM+?n2L8MeG3}$2$ zIu!CAmY}O^3AAHbDVV&HZf%wSDr5tce*mf_ZjUMm6XW{37FC$XFw1%(6a6xYRbO--&OTy}H~}LWr=zZo&hjHmXxZPjpGzXq=A8$ZV}u)HDB!RhO0Ue zz$QJaQ#&K=E>E}1%+O3W)LNfvDEuLa)(Q55)2umhhr%?z5LY&?NP0R`FEAcB`b$xG zc0?S*p#O@%ZK+l=eG;#t+4YHz!XJX@onS9X^f1x$Bo{LBGj7skN0*?hzQ99|WVq!= z*u^BbA~C$JvuW{xYXJE7fnsfsT~#nJo}(X!8H2+_lg-o=8a=r9@f=JbG)hbdta z^Dcm(!4j4%Wu1EYiUae~7s#bpzjo+dh&2qNY?}lDu9oH6s(8V-Lw@*^4?g1Lx5_mL zw!X3veb=Z28w|`Qoq~|IeVHx#dVVY{2keC9=z?PA3qFe_lL1tw<;P6guyY`%dGzhJ zCmeY{n_b{ZPh0?nk?lYKG`rxf38rBri_`?i0ar^^;7M?^{%~u1{{Tnw`#T3upX@%i zli%U{t@->E5v=gch5Xop14Tjv7B<8EvTagi$0q;%$zuzbvTlpPQQ*Dd2Boap=fB~q zMjLrg#FggIrPQMu{G#Nr&o^B#3Pmc~lBi+4s*=Ko-2hOwRWwv@KDCqvBq4$R9vx{6 zGY<9E_BQ-4_7Cnsqg7RIV~CiUKA@rdxgUvnbP=w0vDUD$a9+B*hCa8y63lY0&4AYF z2&Yt{Q*)B^sWrK*IBzfLb zq=>QDe@MOv7&((?G8f*CX9qW1BE*cVNizw!w96Ka-Uw1|TmZ^>>WNC{ZK=gj#3f#K zXi7jSdS}GfHKMJe0GDsdCaNw6DhxHqP_6N{`+*4jkB7%o*>q3JL{S4ua0%?wkc*%TcAShD9L>(!kstK>}xGX_MNiF+eDm z$39wa(!|5*QCRvp-|!yMf{(AKy&KC$b46xC(-c^MHHF#!G&%YfwS~a@d_I!kLzVn9 z1ma=s|Bay=a6K2R4{Qod`Jb(nf0vhXINaIhnCpa?Ktg=trK46G?l?7!?N<(N4zhW3 zqkH{f>7<7e--jmhG~u9&cIg64kfEKv`p@t6Zu`M7N%040Ka+z z)(Y6L0PdRQ2i@oyEwP_v#F0zd=*d2_1|C|m#D!o;4Dn!>ofO~p>TUGvJ{;_A7V&|s zwM0bPch&M4-;oKNsUvSn0o$*t(k|S5FR*oVE&?aR_1A%Ypp%-xa!rjosXMv8Y8@M2 zv>~&nEBe<}O*)IP1x4o@GBl2JE*TnZ_Wkprl`&z3WZYwFHvZJdbF1 zekh?sFG=}r(=L8!(77Zrwmbr+89@!sk1;)2HgxM1JC!L~`0|NCgCKHf3hJD(^<1+& z9I&=a3bC^7)A%#gO{%uBg`NJ03Va6Bx9T8!@FQy#6Z{a>Zn|v#OYYs+h~eR zK35xl0}e>zFE%rkd^B0KGaCSUQP3JC%? zmvr3a{~wqGFbPA#HY=|N?u&L5PkBt7=`N!~T~Y>>>tN0&JG zrBnQ+;P=@LbyZExq(MN;kyAYRg^QW)FG6W!*;p{VhV0t>gQ4ViewiHMEEk9Idlzrt zGB_uI&_j0TZ1Uz^Z|js7M-c8~F{sU4#@OJ z{TU^W1|CtO69c(m%}12dRS0gxjYO^pYiptmNKV*=axi%>rLzd8oUUf;TUSC!GL1AD zUsKwePU(?xIPXo~oSxGV>0ZT#Er=Py-DM{9RaH&QK9!P$sFE6og zBmv#XUja=#I==epCEs`AA(h?n`*aeD;KhC`1{wWYyal9RN;q&c;M3Y!flSlkb#i&( zLqtmvcayT``@{3|>EzYvWO^(jMsJaM}4IK{p9DvIUWOg)q9m9b@UG9puhLi>Cq`R_wT+$EV^}6Hq!gQ zt$b_~e*k)1N+_1}`-c&V9hB+)%tu6B^Sg|;SJLS-7KL3szl!|ZS{0%z7FaVx7eccC zjQ8nya=DvPBFtZ zJB^4#KkPi*-G1^ECL7gLgXNx2pelHYK{N;!#@s68VI?H5gg%$e7`NH5@U(*{tMLh& z$3_t4ON0e0#tw+6n;eimy;uz5N2_ZnoMR!Wk3uZZM^*0L;r~`I3r!@h8=I_v=dlIg z)4lU-d*|tXVE|4$iw_*2ta0Ws+Fj`Q!?V02f&%DE?)!Wq09C2F_vf9Rr=J}~|6YPB zRY|IGbv_AA&8{OhDLdg%Gmr(e5F@bIYo$n@|CO+8V&b;ia>!r-)ST~mpXncF8icSo zW*ikA-Y&^v;VZ7o79SFpqDZmOD&|+~7l0u_Xn9!{0Ezju>4|4Nhe?j$e9dJpPiQ2U zIh!~;IC?$k55Bti8quP^I{w#2V2v)${2QfAn*V`!WOH2OrSGQKFE(bMwA>MIcARI2Vpcjff)=#0VX!? z8b|+?STFcX6!0j0LLQE!tyZF0;~R0W3);SuDIJ>RPum*=M_1`p?6<@qXX@y#s0V5Azw!|n7VA1M9dptCys}v zjYH4Az)31&F09uccj+@nJeG|CsTxt^@_V$gg1QC76vn9HeCLg$m>xm*Z({{vDU%3R z&bw8hZ}rv^7`nJ*Xw?;^oMu(IsYPt5>J%77ej)o@ENBU#bQTI$El7j{(2dCgg-R9_ zNsvEFIZ!m7B|t7d&Oc@LN8%82$j>StGcfre?sDxyPGEjn7K{ED<9WuOJLfcJP@}Cd zx6qjNL)zy~53Q-YR_Upg=%))vobMU8VFr?? zc}|m1nc>r}_rqGyd?L4xxKo7i)#8@HWY+`uIJfH%n=@NW6?+7k4vNI;Ngb36stu-8 z(A?qS)?@fKNoTnj7sD(P&E(DqvV|66r(v$5*(?_emDpVFgYR@g{2Ea8m`Y<%?6I^v z-I(*41XOjW6ku04UBO@3;@ta#3XKIYgiYPjFcCKqHkEAI(xU^byNMC)n8iBBCsH-9df(xiFtkPHW^4L1iiiH_oq$jFqP>}}r zYx!oRq;@S1JYtLflq5)-`GyL%4i!?}^$C%xe2%oJnOWDN zgVbA}22%VZ_up&?l!MYEw|7<7e6d}viiK71<0w0!j_@44J=Y;3D&+hy!HLJ&$&x5q zzPz~4Bj-biX2H*2V`Oc9;22p2gQyJ6n?ez=zQx3r)AVjp=dNr9l5yofe*Hzg{Bx`Q zW{5=Wrw0rIi!`~;DckNe2!EW|Qi{wVCyW)-x82Dw_cVke;n=?4A z4*xBJnCZ(FF@nFE$y`=kS{42Lf|zYpo!Pf)e(YPtIs;W<>-_xiNFU>n{bsS&%lQbm zjHR=asf?Vo9sZ=NTlqDN*{@;DDlw^2n6x$Gi2WMIY(6`}1&zX6xCmav{oSu&%+7iY z9ronx;RM$d(q&(+x!j+LXv-cZo-9gi%9pRT!kFz%rU=4@oBG^+shvIDOZzo~*~KD; zX-c724-P7k$TZSb*m)Fm{568vuMy17OcBX@M9foKYdCM^IAnHuc7B5q%(kYuY2Aow z_G=6?H3>M~v%QO!C>@VHKo%Cq2PkCRa167p+&a);d)BmlrGtrR-7A#e`uT{RvhiKB z&nSkO^ft#?3S_M%{u;wfgA|KHY-5df8!gAVM!OpebCiL1M$oxryf-_~_b5TR0rn-Z z#8qOLH5iPFWe5hxPtl_|&-_d-+$U9=F|c61-*IzI%geX%zAj-z{k?V!v#pX9r0Y%G zKbyWTH}l%?7zWH^;jJGe>h^Z%$(&bBuH9-^FWZT=eNtQsRIFHEM} z=Y(IQoK1e3On;4X_G^^0U!$B^$hg9pD?OW~fmcKu%+a_!u+*=i!j2B7JrCnG&f=B2 z=ygJcnF=Ct(LQlFu&smSR8&7k&H2>>)tuct^j!Hq)a3&7w2p3GJApw%RF1#js1ku1>j#Ud6X7!s8IgKcL^>$k_srT~)k#UaA zt58mHk7gH$EQV(XU!T&Y&;#YrtBVJ>>q;@D_TR-GQW+`;kbhWdD|~Q8Nvi{zm!!X6 zDQi@?jD&4={xg=Y(z`24R&}QD5UrY7m#r1A{ZB4j4d0uVuHVm2Chri{33BsnayWm9 z&|&6j+B=fqWG~-ICc^(hwjT5KR>g#;zgZjna!qZ}D4XZqKdf+?oSht=P&nbzNs2jm zg`lzN4oRlj->F^|v0Xr4bw~m3?}8^w-Jpk8e?zW$m)WCEj3u&ydvg zMF9l&Y!pP!jwVyYB*yPQAD%K+^ZnV}Zd|gczL&gpP6RI4d`eODk@EfQ5W<}hemu*+ zAI=UvG5vf9VI~Eo7~1N?GdKD{0tUNB_s0q)%wsj7lDr-)B~`RT_CbC>xu}Epr}bM; zQP+Me+JU5}?8g{L(yv4eELiD)(JfK1M$%?KVw||tC?M2Y^;C{!6#?>+A)1CRQP5IS z#Wn?tI-sC_%i(1p;L?|`9YpF%gMOC3F8v%_izQPp)}mhmlhCTtFZi1~AG8`C_Q~PK z*Zc{RdrB24&(vZvHJG$)2%pX;Z=r3a8H5@6GbV2OuC`Mau_+7Wme)$TagIykG6Xr> z*>9cyRdmRT)(;aIq?z?3q*NcFX!!z`AiL`@C+QiRST=VKk6v$6 zPa32bW(M*t5mw|EgR4wWe<~v|5s+v^MPDQZoxv?hulnPe&Z;7QlVpB>`0oCEGWqj_ zCawZZJ3A3bvaE_Wf&S_@%237O%Mon=<8~5cw&Pi#@`uod`uln@P-6E?oklD^DakCXNbOK1D9IGk|XkTuo2^irfiakBfbuS zbO4p0P<(I$`ke#=e@X2O;uXgTefh2N$eqx}Wj+H08%~4qqn*b;9z1yVNe zYeDabjNTuHDYGGt)R=GsChp^hrW~H&Z9*xHgQNuYz+Oj6&>I*HrOHS~GhsCpy_?`A zNs36CmR_fC19?mQ=hO4o5JApJBVOy)`7CU5MK0N!Vi2e3`3Gc`1>5Mc5n~uh z;P+ClNvN%0omB3l@=t1&*nK1>N^M?QqL7m)z}J!};UZj1v^|?FaFcsFt%)u5F^xir z3z&*O2DnyhMagigj}`mh6~iuNX$F2P7KJMgeiYZs`xIrXEE;78kR5xu{(7rDPb<^9 z>t%~nYphu}w`vxxUw`2wr>(VqU5CUJ!7F%*MVyg}F0$T-Bd}wZ#YoZkhoNs0CO68* z3F-<;r*fZw+^KEi_(5qNC208lLH8Nd5I1*iyOqVQ?B!GkbqUNPp*}Z0eZ0abyODm_ zeqmqb1<4^H;I+z3=)WnSKrBd7ZdvxHy2Z=8)m2T#O|A4q8cPki`m1U>;x?r{mUw;n zX3GC2nZ3K<9D_x|_ea1Ztw@@+dxoYEM|Yl>%&j(AC0l2;hnl`p4YW%8`X}VfcYxR#zB*!b#h-o)Oq97s2 zz}J%!aAD;YuBF;Tep6RlYxAie2ork2zQohZV*O)@J zjeTQeOw{@7JP^1wW1yHcXuYswI-CZGDGbl>#E2cY?>6pah0r0ble8q8ZqA}Atme+? zY@0lgts-uCG}IfX27|k!J7zK|-EJKaWvfh&1{|iZtQ;kf)-NP}iBdMnXShZ2YDZO$ z1PqHR)`H2Z)Yqa+6>OouHM(BrC7=~9;7Ang9F2<3Ol1Lr3+&z=6muOck?>VbxC-&< zW^aZ&Kf$Oq+@JaA3MpX4^`gl(EOq2-RkWJ08pmaxkX12JWg*3Jlu^+$G7YJwa@H|O z)2Zl-MtiwkgmNjuxEKNS7}XC?Kft^~Z*$MG0_4hs_kuaep36kq|IAMT=0PzRhF8(s zVTGCh6=vvEPT{JlaR6{Kr%w~u02*G29_jeCH=kYT zqtP?f^|+9PF#&D$oJUx=VKz?O)j^yZPZ>FM{*~ltOsY6VWe~16vkXKNYFIT9h1pf* zG{16^POto(2$HB!bWOC0JjH6H@ad&6OIjGv>vb8^>k~~O?Gje=HK$OGJjL|+U^Ke9 z>2s|5-hY}lCX-0fCNEG*-Rw*fiRI&4Ta`52*-dehLK}}=Ji3sUe#)xT7T`tRWlkUq z&JBloGKmikR9N4=GxRPBn_X!r)dhscVzZG_IvzvBpK%V1Cr*hL-*~JxJ{<_D{}sco~x$Uf3VYy;Xsbxk_d#t}Y96EkRgKaE9~~ z&2S=+P%4HYIct;*$?G3ibxC0n^V&+|U6te<*c){&JAfJuR?-a|7Mq)kf^Cc;UXTi1 zOe>9Z0sp3vpf$oaE>59>^bPG?+H+49FGvL^ZF;lDliZuUTq+}mDyFWrIbK3KT;4dM z+e$q;NA$XZpOokz|{}<)Txw@)=9EJXkY4wpC|N5rfy*Le-!`oDghbjFE(_ipeut zL2yJv&h-q|I&}0@6BRHwsYBkxBjIF93!|L$LYRXCGY1C#7ikf;#92kH%?YpX zY|uF^3Uiw@e#4^@8o&T0T!kP?G+SblS7*6!WJ*aNRxC61(yfIi`8f-L(=ro~Djifq zMss<{hTG)}M8-@VBLheutXza5vhcIRH-c{-&v5kyR|rqf4$mjpw813_588k8&j?$@ zw>IwhiZV6hm`;UFhwsdAP3We?cP&www%(o$+(%xJ0(m<|EUrtT^g6}>v#94<%0Y>_ z!-8!^3sTgCSC}RrnNp3FDow+*ZOv?{oU5}|%LSAEHMxpBTR?V_T@$WxYl5a;uhNn) ziP~(NO{SvsUUK0jQ*7FO2X`ND?0IZ4T^{sBFC_Z4J-J?|_{z_?)alhb>4f}T5ltom z&bR#r`KLIr=g)C|>QuNrl=O8e1dc2{SQwu+eG#@3fqxKuqJD0}zgh3ePjPl^AtZrk=(=B;6senOd z-8+YLHn(W;eZB3Ld*+f6)!K;d1Rhm#qrOJs#CW?UpMQBZbD^!U^hSRfV!{Cc(Cn36 z0bN!#Pw_>gcF5@j;7n?VU4n}=r74}#Nk8^1)eA)Xf!hkjm zwPSC`3U(N=sR+`Yad%k+8$G{B3D?xZ5aZ!#3t;fBU&tVpsU%`r+jXWlC0~yHDp<); z|KZumB$wt^!L$Ore%oq*q*L2{($U$5dE)!k$)5F!o)XefZRaMS^bS$QQ=ri@E_64z zav3jWmJ6#(;HSR_w#j{1%{y8)^G>ddQyQmaPHgc{$O02Jw;weKD0X>P&}MpsUwE{T zgY5WZJ*J5GUV<~QkZvu>LIj~lyRwgSy}JguZv^n zRO$+UF1(u7=MoqhLgq?CcMpp_#-JH#k8ANz62ma4QUm9bcIFjUxj-WtZ=^9xD;}=e zIzmvcvvbIe^eZEfI!)Y`F`0GHW1(aSAGLpjbl-QFF}Q3WB%+B_e>yokoSwh?$X#Ij zyu}<*Si27~-nh)pl%V0u!Hc@+cSkNy3T~>}(B)EkCqp#iX3J(kxO;aYT5a0b;BD`i z?x8fy;w>GZqMP0qb)}c@f$lx22dAcy7~NEF4)3o-WM&NuN~?>1)|4c^2<4zqM~v30 zv!wg3f{cUXtY;e=l!69}?V9gaV=t_hS2yD)?QHS$pgm=#+w|ng zZYM`BwHSoojKTego*7oJwtvy9nU(nRCY7nyGZlW;jXI+PRpo26u=510B@8VzcsU;2v4G0FN$T6`sc}F5ul2 z5*GC2h=5`8+HkP{Z0qsf(|5ZnU>1F<7Yc0opd`lt5bN?+ebvSSF`DK_A@VD9?99! z*<^CO;D!VtD9^!43*re29s=r4FL*H%zYw+u!I$687WgqcTWn$`rD=bEX;^|F2qY?! zph{7}FHGc+PYIK!yXw6$KTqdeDyIQfAOn~8LHTn1E!YaaTEH&w)nbF5FJCQgsS9hC zY{W^^GTd|z`_Y4Edk}6aLu}wc1Ru+R9Ju-y)WHHBE?mHHtu-q!@0CSTfI)NnR7jJ! zkk?%zWzmhqUs6)!xRqe0Cngzhc|ieA076>Xie^lX$0?+OfQ4=?fA1ZhA7<4Wtyb3& zmP#k9QZF-uvW!6!zce%^W#FXctq(S!IASqsr;Be_KCSi&dci5J9I`g!AV+a7TJaia zz(ib8sB8HMV`b-l)FzUGUz)V5f|!rTv!4-`2i|~FT*!U}H-9eMVX1J@|EuKxppsvv z$<>9vRR2}ppUggQc`w8CnTz^+yL(T!_P2l7=@$682*kc=!4y>+#lu z9bJpCVCr^E=k&idi z3B?-6qC}L-++y(0-oL|NWISjn)kTI@E=khhqMJWpjhRm5hSy-{PuPdZmpOzfV(z!_G?QJub^4vXoe*BMlRPxV3xn*bt0Sy)FXeSWRB7dL?M zuWeEYY@K-mr0z{#9bQbs@d-{(8YzZGD;?`WEA4MLfdWr(8aOPvk!Y8_G!7K3J{mAG zHitb7iHv(5+Q^e$4VD)MA4~(Pazu8>MVc_4SK>#m5*i${MEK5y91AJoA9@96GM{4t z_Jaj3NO3V4olMRqbGaYXo2ek^opWW8;Ngc;cv&J8;6hhM#HABvx-4^@&)$0X=qLeN z75%T%DX(N<6vPuXRSwiBGn@#LD%<{R5aQ>vmvw~- zt9$2MC1Tn(tO~lI2yxby-J-<#Izd488}hVwM>0joVAau9M6@w;t!8{lN(1IvVlx2( ztR{;kM&by^6flJk8WaN(ed5%Y=w@Ht`4)Ox6RcBcfAZ$-6y8O!Z#?h+bhE%GIf5OXd|fY%T|B)pQ%m)S3{`{4fvrV(X-oFb0;h!6aSO)#4!Ln#hJ%=zSG zHh(u_y~nUs%1jcj^%WN>&zbD2{|gVyKVoW`HIngHOqeUbvY2iZ_Gw&7?Zm>3(rmw! zag_opO9UXx>!+`Qu@`Hq1gv6vkNh)GM)RaS{YlC2&v)=YZpWDZVCI8Bg>SrW=eeS;Pb4lTWl=IE{x1z*2UrE=?-r8uen!c{mB1vc%8DrJ*jP8a8`}q(?d%`qBlmFnOm&$;Xz~3!f#U7ZB%xqK;rr8z#cSEEr%`ty zw$8}M2^U2jV%R&v4N1o7CVM$0&lYw{j}|9X53*=061HE{>$pgAtE6y{nGxk;+8^8Dz384$OcELwW51U)87?dk` z12}+j*x53%upLX&04sW!pe6WuLz0ptdH^b@nP5&WnRNDH;Nl6g$SjfadMR7SKs*#! zlkKJTKgv1ncmhtJ^epex@K6*N4^)){ot31!Z5s#{Pq40_@oAE~IAiLr$Aim+K@VoHJh$=6xHksA-mS^cvCQgkST4L-`ulkBG54S z``ESEHJO6$oQlK`uBJJp13UGRo=`K-flM}r3^FD;I0yvPbWww1kYMv`X^gL|Y@!>I z`4Z2)XV*b-AM@#L>H>or!Ew|ofN)DrsZ0pdwgGnAq~Ur0CV^f z#Am7!mKZNTN6WGWECrrFhqK1OXKv?=E+`?pqYMYQ+Ww6Tj>^r5ge(4u>GXc^j3=@{wSf5M2 z%-f7#gXP+U&P5aReX6b)n-x7ulBk?Ijeelt>Gsng{W9K5-{bUaDKIpd3SvbH%s)!$ z0)Q;+3-k8Y)BPWx?HrI}DrBAL)q7LWkih4Sov$jBDbyxFTwAP~PrcJqf6KI5dznXKHP64t^fZtpv zYTvoQGk|Z;FXj`uOC170EdJ^Z;tqy@Y23%7#+Mr)uc=p$BPZ-vFl2>~3s!10@N_a? zzzn_dh5Z=Y-{xrt8_X_F>crznxa4&ADnB5OL*^IZID9fBelKvCFi0%nWpjJ7cH~c9 zo0A)Yscy2vaZK9S?H$Pz;20z311~c@zJ}%V{Nd!M3A9`J&4q<$DJ%wy%=GAjhwqvv z<+i?g(5NoQH@OadwyGT$_=DVxwWzIsLvrxWW9m_h8l(!}w|sMmi%-~G2=`&y30y&9 z_zkZU1}Jue66?J{!(Fqn$=N~Z2DIi+O&=14CqkB+0UWhJy_ z-w2WuU|y5CL2kB90>*=CdFCh>bZN1SJ5%ouqcJaeyRvX%Qp`fTY%_d#?bAMG->~-~gZ` zN~ibV@XxT{{A@q_-#Eh0{xY*}>rw#K)3f)?-qSOZu&Oe%va+(Ws zKz(g%u4vr^WUO5idA3EFr%^O#5v7_kfo4<*2-HL{r24J`%`?p!qtVGSy2AwytfXsb z$i#&L?@;wUXSmJm0Vy zJjy#`hH8F{N<(-+192mGPC)7VWNeG_ zle)Vak`aP!vjh7RcRJsUF{+H>BM}eQ*p1S;`ph3|2{k@+OvuYA{<4 zCw#Th_!74h;UsKUxsY8N(2Qlm{8NZzK&tVM9?eb{5u}rpB;%!J(8Us*MheN{u_6s* z?Myn)*%_o;FEDDdU`UPBw-`3{A);3wFGF~Vl)sMJg_0wge5q7NAByJPr&wE3LDYgW zLePC)S292$;4jVXvs$I{A}uWI2_)Jt^;Sb7*`AVAlv5hJruTwXdpcjAk}udKxecHg zi>GHp&poqf0rARIga@XtWcSdf{|#Oz@Ru1TYl*0xV|K`S0EhA})Cu(=f;ZD+yfIdM zYr^#ew@wta-9jrPfCARw+vTX+P_$YtS=ZilAokowTCZ5RS|BW6RiB`!A@`saX}=t~ zqF*vq=5thP^E+8wxqT91juJYP6bh!!9(61Onafr^%cpMRaZQKcdhV)xI2kT(aK8eL zUg`)ocguuJnf;8v?mQL?%9x4dF|OnZD#sPEVvKVng~g4Ueoz%sGk%#od6ok*>|t88 zV#Tm3^JyHS!>l3#lkHS`B{NSe&q^dQz0|TuB*&$Y$n;DlpT>!;d4g(oofAM$D+7w^ ztdd9vcr7B^=!c%c+N=@X^`dx)lEB)flL(qjf)RH%U@|LL1iSBVAS#@M~kWAGPKF;dSy;F!O!)(gb zFN+Jem$^p83Ghjl$?T|ZlFXB2J;~txTQXcpuymM@GFfy9kLmSX5Qjn-f0zosOaUdF zrOwu85){#B{A`MGERj^=5n${iV9}+zjkH2`9xijoJXNeTvrZO37NOMblZ?Df@tlM( zZl`j4xg64q5OVcR%!s6ro)8M92Y_#@9@z9Iibpig)E3D$&*z~OoN>vUm)$0`~^+(4l^J<^m3V#Peq25k^ zoPevC=K`y6j9-5rv1=Um#$$uK_>_%{74`f3h++%*J`X!f4fvba^IIEXA2+=myguzX z`RODLe9#`W>3Wqw|IisYr7<`;JL(7e2emzBqoMmP{y5Tz^C{&SB4IE301vZ@U8PxU zMjp?~v$OD8^5t-W%SN^~Q!G)+@FVV{B_9*{@9Er4EcfhuU?Kh~*Pq?tCM^{rwaDEK zA2~+y(`NCbg*OX^R0huhT;sT-R6#Un!8nu}0Id88HxET4dcwP0-h>POdi>#qfS^~<+Xf3_ zf0LH;uLmcm{qi9&J9JiUfoU=oPo%{B=;GWA=s8fYu0Mzbu0NEga2i;dx^(~)=d!IP zd!4Lg;+y;prrKH)MJ72C%1er^SkA&z@*{jKk7 z{4e-3saB>h4myYJ*QbLQcf)&JWQVrA!k)ePnG13_3!lS(@3EP`cojW{h;G8BP1vmE ze?~4MPG^gV+5m3VEV!xyBNjuxmLMD;BxME0_O77xFZ#Jsu2QYw@BaAi9(uy~N@Xbu zDOW1x&=7Q?hQoBC+W+VO_~!rnfB*M?{x9_BpOV#P(IxoG67JE-;P~|(b{+5T#>?C> z+^V^ggO|BlrPe4_YNbkJP_4hj|0}KMmFmk%rPQcbwnw9@;i$g7ivxMP&1%#b4!4Jm zQMEaYE{CmZqjf#py`-i>OF;=aUYx~rfMMnI{GyAyqr$`U(}PY=6jGQ?=LVaqZ*R&p zf;%Z+4m}TiIOt9DE#~Lw=wSL{lsNYbd_!t=ux1c5~#x) z`6YlC?;RjHA-fVHaKKeC!gVlQz>q8p#9G;y5C;%Fl?35DMWhbUaur@eXC5XJ(ofhq zPR)^3qVDg~QO8ep*OsouM#6!jeYw#ijMNz8?BtPcXf&chP`RtFVbb)3lKxFrI76m| zZ&MOAHE^tw1b$`pn+&5gn&lL$hD|tTt<`x(wL zASP$IrBzDEr}YqeH}I)p=j?U2Pa>kqETUzH?)6cV)4kc}&Xi^>BW;Emy4*Xbh{?Cn z@P0NuoJSGe&3BF-b2y$yPHNSr2DRcwFG7qR!vyuB!al}O9W?0lgQiUPcc0CA(U7e! zQNDma;<695=@5V_j&P15DvZePJ>CPLJsouxSHt^=XyX>!*(Y8n>U}S#i1uVSzrzL) zU)n1Ga2Ie?Z)ZR9_0-_;$IN#pY`!$r?_$4*_+*62GtgL z#hph>TzWK{2X%m+4ey7OJI-wZhe0)?X)mD86g|w&3F>c%#&RKGplhkY5EfA#i{>}JaC%2l-I*3y8U^u_q#!@1%^TY6h>uE3C&ugwgbWgyroUwRq);1CO+p6<3*$i)+)Q( z)g~w=8LI(fyT0A1?vQNyy>>TfRCh55;j7zg<8!CJv(u`Oc=)s0+TPx-Q6ru7Ldvh# zo6SapnhED=Ii{81eJ&f@-v$P!1nm)dsez0lMa?KVFu2Y^U0JmndwJY$dO)#P&@=(!ZAvM7Z z@i(CE0ei@aJ9V6Y=Qetz??$y%RgFX6EU4|>O1<8a0;%q`R-@LaD#1QdFPP5mW)BN3 zcEDC^6_O1&)QMF03jRV-Qt3soQ)$*KEmR#W&F1&fXcjbTyN%t}c8zq7yH~wdtJZ4G z#x@m?k}y@bZ6_tPkAm7RY7f~~RYlqf9<>Mwu(ngvTKOo#=60#nwkx0)@4IyBh|HVV z7wONGR%W^X4R`9lP>V8Zth5Ss{w?i=HXuq_Gs;ERf%W``4wRu?8DzCueofhizZ0V+ zE&SW{>u>JG8A2uF@b%xooWiUm>`(?X+N6BVG%gtkpbGA#lS2s{yOS_5G3%0PTKsl} z^=>vL_bUp+4DA@+s~~@7ZgOiMw)cZP{f#AC`)qL1KR&tW4|*rBI!MNdxxhf0>*Ef( zTO26dXuoa4BIfxYd?IKwpko{S6Y2ZyK7Mxk`}DR+9+Mm#OhtlRET)rbs}IYG#`b`>1L!x|&U=!|?*M)+?%MuXFgibIQ^} z7}`Swe+%7w*L!{Tw*5}C(|eeHg15`C)97vs)`FROSKBv zYGqy7WyL4~S62Xca3W(eoA#&=trn~cso3Ue?Vjg=w@s!}^m*p$^e!6WRO@zrXH zsgW3Pit=uCyIEOP3kQQ`#RzHh(3>rX^GDz4W^>E?v&q9<=J=QbDj#fD0<1uHc2|{G z_F8g>pjc<3mzJUfhO-^hrPtDC0+tWzl?o4wt+bKyV6-$w+DIiad=@hc&al$TFgV!= zij!7`!Eq4WK_W}PJfPfu&7IwyT3W%pC?rkBS=r_;N96;m1>>acY`UB$NYojvqz=3A zZ>((H7zS5?P*yf?425ezC~3>}Cb}74!4YPB2zIKXvdVAZ0?p&Ed}^4{5U#v1iC0q! zKRkB6dhneGdHlfh-@}m~vKnfBT6YjE8r# zDXOA=q#)1X0!|ATsVisfv3z%C9i$W`4k|O`lxoC*#HOW`Ar7RQkw=!)@RbS6%Ae|u zaZo$I2vU784k-gWPl{4KFAfP$XQ(@3_DH*)l6AfOWqpnGUyR)hypKgG&n&LAft8SQ0GH}+=St;Jmnl>xRMoZTx zCBL(6!+KJ*XFHOWp}}XnIxD5}XBC?jm)`q)j;{I7X_21v&rfliExkz4o)0fEq26rw zHsKb-GYJv;5Ie-6Cj)$G=iK_Fk~LHy?hS;pcv^ ziTiiwlW~NP1C$$0mITrPb5}P6aR|<4pK&S45+6sw=Fx0&jbF#X<}t2bn&9V2u!(zg z$AtDnu=&FSh%E{DRj~OA7a!x-X|Q=Zz9WEcu-S#<5WmiX&9m9Zh?Z;kIuAC_u>(p! zFM`brR1UxX5N!V8A-asN@Vf^oAgYY0^$|dUryJ%3G6*)YgZF`wUk95AAiahY4IgiU zO)OKelZoGNgUz=$h<1t|@$qA@`Qv;@zupC#?=bI(h?zg4WgaFE_@zZBC&RIB=XfZz z=UIDj4v*=$CdocxP~{n?1TvqApd@V3;TqPoM!VURy9)v4ldaSz|k4B2hP$L-_I z@DoaGml_@9zZpZ|XtVYYeXh&#a6X5^g%~Q^Z+7<1s4^;zQc62}Na){>5&Rb9_ewQ< zwLgw$f5L`#eji1UWY1`xC8dDt>Ne5n3_oCMPPJ%~v%UK;!n;WFEo|IVQy{yA+zVWj zy_})_4u#3Vn3dwNL;~p#;q#@e9RfX`&C%_tI@KNesj~dWq%2+SGqQB}1F8e*yStGP^`{8^swgJgF5z9Uhg^?w4vL#G~P4`1z3l zhY`MqCVMlcP+KTwsZP~CU%*#}(%zJ6qy&3Ic!_~~+$#M*pV4q0 zV)8P0fZB3C68LkMSl@%na#6?^^a;AbpSUX!N{fo$r|tr&Po*#JKg=imRik#t&f{o| z?saln+NSUBctlM^pQHy}T<(Kvg1>gDDV<_G<%)`mFKSTjP&aNbjK97c9q6sN}zl{h5D&>KM z_WHO~B^}wHUCz;0+xWy+`yOEEvG?7Nq*$hnVAD0@W5oAOV5Hp!@yCAhmR^-)q z_6bDJ@kvDjNR+$}GE~hylC316TuvfFuT*QcsOZ=GZA`JqD|I@X$zBhFhNwZie<+b1 z!O5`^N6SzGo48-h_<@a>+iL4#_6hJ`HxOgNaYl&jp*Wp8#K^)FW4r$bJ&l5X^sv*% zkVUra>&7PzP{R54jXUf?A^uE`x?jSU}+q`JLt8;?Mm3KHmhM5 zUSdng1K^`4ocG(^u#HDJVIe>hfkI*bc057}yOqhMrEP$73OL8WIRehT7wDf?mxOc1 zv}+q4p#_?i)4gyHIuv$qF}c<)s)XqS^wGDPS3ZAJt%UvVIqec_s!sM-oBxZCnqmTN z-SD=xcey@dk8)2gzX>y5LO#} zo&&qauPike5<@>jVni^Iz7mvtI-Op)ciwgSV3K_$Vd}V6C}pX{GagRALg?X*h-?nd74%+R{D?yY))jX{LC(4_hg8&9RFn z$%o@eN|pv;`}7ahmNUxuAf7;RGW-aOZAIyjLKZEBJeq6dbrH7UyGSVrCHrt~kEI@G zk>lw;Y}+9Y5g~)6`E7{!PN&~{0DSaR%eC9#8?0Z4Qw)`@x{Q>yYIA!#Jyf>a?VP}3 zq-@pIK&gPeBc+kpEBFsE1;auhB{iMp|uyyTi6cac}+>6cbNk~t^kN!yL?i{CDsy|nlzo3 z9RHyI$tu~0 zYf9N~54z!Qmb_JtkAjrmy$yF9nK+3)JlhKnuxbg#UQ{aKDUK03WBp1nA8p549(TKM z-_R`EV@;iWA2q2AP1^U5+ZTBMvwa%k^lgZk{8}Szg#ef{fH?r>-YdxUCP*z*d?P#7 zFy5ivioDUrL4kpz_o=MaHRZ`}HGG40m+d=3!^!f|P~#>|8Q)@SM;nq@vJcm2V;7Dp ztnJxBgg|{qtK%SeBn7MT9D(>~a>4E1;ZC@{p{XkogkK1{=1`=}SzD%mQVLAMOo$;Z8-&OntZBN}Cx#8S2osxjsr#$L_0b?Bd1snbKBcVJ!$<3u|HjAibcAW%}rtdSJp+X=XJMCOXLQ z;j0$v4Z@>0=YKpndDRZ<4TPFyv=2_qYM9)61>|iV)uV9=Z$7>X_u;CJ&}s2sn(&bT z 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

1=W>%dCPl3G$gmylG>udMtc8bbrh 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%0AZCu2j{!sD*1MFeW0HFRzDYgFBhEth&O`WCK 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

  • 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 ){h^=vPNNh!RDl4i>IGx8xsxyGf6ZlVY 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

    %zB`rBJ;|mVN!3qRUA5n0(C~ zG_7KG^;DOtV$=(DNF+f#M7M+E{Dbl7xfbb1M3cTIIAc4*r_?kv?3_GhQGSV$JSE8_ zOrKE2>)ObvXdqdk$~aQCNErbpzr==%Ln18vp%n&;Xk5$0i*gc>t3RnJXZ}%2A{SNX z;`C)dobVD{uZ+!+UIVLV5#zSJQPyaHYDQC5ixNd!D>{a|J*$DQ6}YML&r?%u1{+pU z^hw|q8fnpHB8Q5QhGwfpvX-4BtK?F`Eo!VGm>OzjZP$8jI@h{VDM9~-%cO*gY=>y= zu>bUw#_dRHB-*5+4&p!odWZ@$LR+F1;bDX|-g0qTIG63g@le!^d7R9&%$Z3t!J|zadBg|r8HhAV}CL}+sRzgy#ebn$0%T**?pxI z<=X}jXOB)6D&&3sqbBp>)a-vx-VU(&tx}t%E8!@0aS{*kauirrl5yTr%zkDt)@4&- z`{@m74pFlr9UY zXRaS`o5UGDoS*&}@d!i)F(vP8Onf3%xTNHzI%Ru>BYC*Lf-AFkb?JALXwt;OQW$GM zDP&kj$g)#uenr!jQ6aM8e&8p>YxjDE-#D1473IPVT4(ZnkzzM>6{zt^*Npwt&p`wB}P_o-40ms=gOJQ)8@nWe!dj^Vr9BCNu#(3~jAlFV{YC<~(wGlcAIa_!j+9WJ!a4~2Fe@+!nhWaFTfc$e(R zuH#y5PU%uzG1}A_L~Ckvg-$}TU=F|1Se_-Sy$7OPw?iw*)zrc!6oc@~L1gDldGa8o z7YaXxT~ZHHp;7u-ia1VKooSf?fQf{p8F{pyiTrt=V#y(G+lHV? zE~i!mH!I?Tu1#LFqDrxGF~J`jdUmD8NOlNNxn5@=3;TgakEvElr7QH~ z7+6138NKn)*5Ask;SIC#^+N;*yQsj{zWd0aS}`!yE6-_mcnziuGw{Xivy9`(n*

    R0;ZT1Sp&(X}?7^NJbhqF&KiEmZU`BM8&cOcNJp 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 >weqx;WRIw{i>M41b}b^h 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

  • O(!&S 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�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