env.info('*** MOOSE GITHUB Commit Hash ID: 2024-05-16T11:50:20+02:00-830dd05514b7d8d9a5bf6e0fc2decf57d72c5eac ***') 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", Kola="Kola" } 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,noprint) local text="\n" if not table or type(table)~="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 if not noprint then env.info(string.rep(" ",indent)..tostring(k).." = {") end text=text..string.rep(" ",indent)..tostring(k).." = {\n" text=text..tostring(UTILS.PrintTableToLog(v,indent+1)).."\n" if not noprint then env.info(string.rep(" ",indent).."},") end text=text..string.rep(" ",indent).."},\n" elseif type(v)=="function"then else local value if tostring(v)=="true"or tostring(v)=="false"or tonumber(v)~=nil then value=v else value='"'..tostring(v)..'"' end if not noprint then env.info(string.rep(" ",indent)..tostring(k).." = "..tostring(value)..",\n") end 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 elseif map==DCSMAP.Kola then declination=15 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 if type_name=="MH-60R"and(unit:getDrawArgumentValue(403)>0 or unit:getDrawArgumentValue(403)==-1)then BASE:T(unit_name.." cargo door is 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(name,"AID")then template=string.gsub(name,"(.AID.%d+$","") end 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.move_object(obj,from_table,to_table) local index for i,v in pairs(from_table)do if v==obj then index=i end end if index then local moved=table.remove(from_table,index) table.insert_unique(to_table,moved) end 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 function UTILS.HexToRGBA(hex_string) local hexNumber=tonumber(string.sub(hex_string,3),16) local alpha=hexNumber%256 hexNumber=(hexNumber-alpha)/256 local blue=hexNumber%256 hexNumber=(hexNumber-blue)/256 local green=hexNumber%256 hexNumber=(hexNumber-green)/256 local red=hexNumber%256 return{R=red,G=green,B=blue,A=alpha} end function UTILS.SaveSetOfOpsGroups(Set,Path,Filename,Structured) local filename=Filename or"SetOfGroups" local data="--Save SET of groups: (name,legion,template,alttemplate,units,position.x,position.y,position.z,strucdata) "..Filename.."\n" local List=Set:GetSetObjects() for _,_group in pairs(List)do local group=_group:GetGroup() if group and group:IsAlive()then local name=group:GetName() local template=string.gsub(name,"(.AID.%d+$","") if string.find(template,"#")then template=string.gsub(name,"#(%d+)$","") end local alttemplate=_group.templatename or"none" local legiono=_group.legion local legion="none" if legiono and type(legiono)=="table"and legiono.ClassName then legion=legiono:GetName() local asset=legiono:GetAssetByName(name) alttemplate=asset.templatename 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,%s,%s,%d,%d,%d,%d,%s\n",data,name,legion,template,alttemplate,units,position.x,position.y,position.z,strucdata) else data=string.format("%s%s,%s,%s,%s,%d,%d,%d,%d\n",data,name,legion,template,alttemplate,units,position.x,position.y,position.z) end end end local outcome=UTILS.SaveToFile(Path,Filename,data) return outcome end function UTILS.LoadSetOfOpsGroups(Path,Filename) local filename=Filename or"SetOfGroups" 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 legion=dataset[2] local template=dataset[3] local alttemplate=dataset[4] local size=tonumber(dataset[5]) local posx=tonumber(dataset[6]) local posy=tonumber(dataset[7]) local posz=tonumber(dataset[8]) local structure=dataset[9] local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz}) if size>0 then local data={groupname=groupname,size=size,coordinate=coordinate,template=template,structure=structure,legion=legion,alttemplate=alttemplate} table.insert(datatable,data) end end else return nil end return datatable end function UTILS.ClockHeadingString(refHdg,tgtHdg) local relativeAngle=tgtHdg-refHdg if relativeAngle<0 then relativeAngle=relativeAngle+360 end local clockPos=math.ceil((relativeAngle%360)/30) return clockPos.." o'clock" end function UTILS.MGRSStringToSRSFriendly(Text,Slow) local Text=string.gsub(Text,"MGRS ","") Text=string.gsub(Text,"%s+","") Text=string.gsub(Text,"([%a%d])","%1;") Text=string.gsub(Text,"A","Alpha") Text=string.gsub(Text,"B","Bravo") Text=string.gsub(Text,"C","Charlie") Text=string.gsub(Text,"D","Delta") Text=string.gsub(Text,"E","Echo") Text=string.gsub(Text,"F","Foxtrot") Text=string.gsub(Text,"G","Golf") Text=string.gsub(Text,"H","Hotel") Text=string.gsub(Text,"I","India") Text=string.gsub(Text,"J","Juliett") Text=string.gsub(Text,"K","Kilo") Text=string.gsub(Text,"L","Lima") Text=string.gsub(Text,"M","Mike") Text=string.gsub(Text,"N","November") Text=string.gsub(Text,"O","Oscar") Text=string.gsub(Text,"P","Papa") Text=string.gsub(Text,"Q","Quebec") Text=string.gsub(Text,"R","Romeo") Text=string.gsub(Text,"S","Sierra") Text=string.gsub(Text,"T","Tango") Text=string.gsub(Text,"U","Uniform") Text=string.gsub(Text,"V","Victor") Text=string.gsub(Text,"W","Whiskey") Text=string.gsub(Text,"X","Xray") Text=string.gsub(Text,"Y","Yankee") Text=string.gsub(Text,"Z","Zulu") Text=string.gsub(Text,"0","zero") Text=string.gsub(Text,"9","niner") if Slow then Text=''..Text..'' end Text="MGRS;"..Text return Text 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,BASE:_Serialize(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,BASE:_Serialize(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,BASE:_Serialize(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.IniCoalition=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() local countryID=CountryID or country.id.USA 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_VEC3: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={}, STNS={}, SADL={}, } 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,SpawnTemplate.name) 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 if UnitTemplate.AddPropAircraft then if UnitTemplate.AddPropAircraft.STN_L16 then local stn=UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.STN_L16) if stn==nil or stn<1 then self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for "..UnitTemplate.name) else self.STNS[stn]=UnitTemplate.name self:I("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for "..UnitTemplate.name) end end if UnitTemplate.AddPropAircraft.SADL_TN then local sadl=UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.SADL_TN) if sadl==nil or sadl<1 then self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for "..UnitTemplate.name) else self.SADL[sadl]=UnitTemplate.name self:I("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for "..UnitTemplate.name) end end 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:GetNextSTN(octal,unitname) local first=UTILS.OctalToDecimal(octal)or 0 if self.STNS[first]==unitname then return octal end local nextoctal=77777 local found=false if 32767-first<10 then first=0 end for i=first+1,32767 do if self.STNS[i]==nil then found=true nextoctal=UTILS.DecimalToOctal(i) self.STNS[i]=unitname self:T("Register STN "..tostring(nextoctal).." for "..unitname) break end end if not found then self:E(string.format("WARNING: No next free STN past %05d found!",octal)) local NewSTNS={} for _id,_name in pairs(self.STNS)do if self.UNITS[_name]~=nil then NewSTNS[_id]=_name end end self.STNS=nil self.STNS=NewSTNS end return nextoctal end function DATABASE:GetNextSADL(octal,unitname) local first=UTILS.OctalToDecimal(octal)or 0 if self.SADL[first]==unitname then return octal end local nextoctal=7777 local found=false if 4095-first<10 then first=0 end for i=first+1,4095 do if self.STNS[i]==nil then found=true nextoctal=UTILS.DecimalToOctal(i) self.SADL[i]=unitname self:T("Register SADL "..tostring(nextoctal).." for "..unitname) break end end if not found then self:E(string.format("WARNING: No next free SADL past %04d found!",octal)) local NewSTNS={} for _id,_name in pairs(self.SADL)do if self.UNITS[_name]~=nil then NewSTNS[_id]=_name end end self.SADL=nil self.SADL=NewSTNS end return nextoctal 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.IniPlayerName or 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, Alive=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:T(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=tostring(Easting..string.rep("0",5-string.len(Easting)))end if string.len(Northing)<5 then Northing=tostring(Northing..string.rep("0",5-string.len(Northing)))end local MGRS={ UTMZone=UTMZone, MGRSDigraph=MGRSDigraph, Easting=tostring(Easting), Northing=tostring(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: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,Delay) self:F() if Delay and Delay>0 then self:ScheduleOnce(Delay,MESSAGE.ToAll,self,Settings,0) else 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 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=UTILS.DeepCopy(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=false if KeepUnitNames==true then self.SpawnInitKeepUnitNames=true end 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:InitSTN(Octal) self:F({Octal=Octal}) self.SpawnInitSTN=Octal or 77777 local num=UTILS.OctalToDecimal(Octal) if num==nil or num<1 then self:E("WARNING - STN "..tostring(Octal).." is not valid!") return self end if _DATABASE.STNS[num]~=nil then self:E("WARNING - STN already assigned: "..tostring(Octal).." is used for ".._DATABASE.STNS[Octal]) end return self end function SPAWN:InitSADL(Octal) self:F({Octal=Octal}) self.SpawnInitSADL=Octal or 7777 local num=UTILS.OctalToDecimal(Octal) if num==nil or num<1 then self:E("WARNING - SADL "..tostring(Octal).." is not valid!") return self end if _DATABASE.SADL[num]~=nil then self:E("WARNING - SADL already assigned: "..tostring(Octal).." is used for ".._DATABASE.SADL[Octal]) end return self end function SPAWN:InitSpeedMps(MPS) self:F({MPS=MPS}) if MPS==nil or tonumber(MPS)<0 then MPS=125 end self.InitSpeed=MPS return self end function SPAWN:InitSpeedKnots(Knots) self:F({Knots=Knots}) if Knots==nil or tonumber(Knots)<0 then Knots=300 end self.InitSpeed=UTILS.KnotsToMps(Knots) return self end function SPAWN:InitSpeedKph(KPH) self:F({KPH=KPH}) if KPH==nil or tonumber(KPH)<0 then KPH=UTILS.KnotsToKmph(300) end self.InitSpeed=UTILS.KmphToMps(KPH) 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) local Name=Name or"Enfield" self.SpawnInitCallSign=true self.SpawnInitCallSignID=ID or 1 self.SpawnInitCallSignMinor=Minor or 1 self.SpawnInitCallSignMajor=Major or 1 self.SpawnInitCallSignName=string.lower(Name):gsub("^%l",string.upper) 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:InitHiddenOnMap() self.SpawnHiddenOnMap=true return self end function SPAWN:InitHiddenOnMFD() self.SpawnHiddenOnMFD=true return self end function SPAWN:InitHiddenOnPlanner() self.SpawnHiddenOnPlanner=true return self 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) 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 if self.SpawnHiddenOnPlanner then SpawnTemplate.hiddenOnPlanner=true end if self.SpawnHiddenOnMFD then SpawnTemplate.hiddenOnMFD=true end if self.SpawnHiddenOnMap then SpawnTemplate.hidden=true 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 self.SpawnGroups[self.SpawnIndex].Group.TemplateDonor=self.SpawnTemplatePrefix 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:F3({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:F3({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 UnitAmount1 then octal=_DATABASE:GetNextSTN(self.SpawnInitSTN,SpawnTemplate.units[UnitID].name) end SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",octal) else if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16)~=nil then local octal=SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 local num=UTILS.OctalToDecimal(octal) if _DATABASE.STNS[num]~=nil or UnitID>1 then octal=_DATABASE:GetNextSTN(octal,SpawnTemplate.units[UnitID].name) end SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",octal) else local OSTN=_DATABASE:GetNextSTN(1,SpawnTemplate.units[UnitID].name) SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",OSTN) end end end if SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN then if self.SpawnInitSADL then local octal=self.SpawnInitSADL if UnitID>1 then octal=_DATABASE:GetNextSADL(self.SpawnInitSADL,SpawnTemplate.units[UnitID].name) end SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",octal) else if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN)~=nil then local octal=SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN local num=UTILS.OctalToDecimal(octal) self.SpawnInitSADL=num if _DATABASE.SADL[num]~=nil or UnitID>1 then octal=_DATABASE:GetNextSADL(self.SpawnInitSADL,SpawnTemplate.units[UnitID].name) end SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",octal) else local OSTN=_DATABASE:GetNextSADL(1,SpawnTemplate.units[UnitID].name) SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",OSTN) end end end if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber and type(Callsign)~="number"then SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber=SpawnTemplate.units[UnitID].callsign[2]..SpawnTemplate.units[UnitID].callsign[3] end if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel and type(Callsign)~="number"then local CallsignName=SpawnTemplate.units[UnitID].callsign["name"] CallsignName=string.match(CallsignName,"^(%a+)") local label="NY" if not string.find(CallsignName," ")then label=string.upper(string.match(CallsignName,"^%a")..string.match(CallsignName,"%a$")) end SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel=label end if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.settings then SpawnTemplate.units[UnitID].datalinks.Link16.settings.flightLead=UnitID==1 and true or false end if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.SADL and SpawnTemplate.units[UnitID].datalinks.SADL.settings then SpawnTemplate.units[UnitID].datalinks.SADL.settings.flightLead=UnitID==1 and true or false end end end for UnitID=1,#SpawnTemplate.units do if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.network then local team={} local isF16=string.find(SpawnTemplate.units[UnitID].type,"F-16",1,true)and true or false for ID=1,#SpawnTemplate.units do local member={} member.missionUnitId=ID if isF16 then member.TDOA=true end table.insert(team,member) end SpawnTemplate.units[UnitID].datalinks.Link16.network.teamMembers=team end end self:T3({"Template:",SpawnTemplate}) return SpawnTemplate end function SPAWN:_RandomizeRoute(SpawnIndex) self:F({self.SpawnTemplatePrefix,SpawnIndex,self.SpawnRandomizeRoute,self.SpawnRandomizeRouteStartPoint,self.SpawnRandomizeRouteEndPoint,self.SpawnRandomizeRouteRadius}) if self.SpawnRandomizeRoute then local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate local RouteCount=#SpawnTemplate.route.points for t=self.SpawnRandomizeRouteStartPoint+1,(RouteCount-self.SpawnRandomizeRouteEndPoint)do SpawnTemplate.route.points[t].x=SpawnTemplate.route.points[t].x+math.random(self.SpawnRandomizeRouteRadius*-1,self.SpawnRandomizeRouteRadius) SpawnTemplate.route.points[t].y=SpawnTemplate.route.points[t].y+math.random(self.SpawnRandomizeRouteRadius*-1,self.SpawnRandomizeRouteRadius) if SpawnTemplate.CategoryID==Group.Category.AIRPLANE or SpawnTemplate.CategoryID==Group.Category.HELICOPTER then if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then SpawnTemplate.route.points[t].alt=SpawnTemplate.route.points[t].alt+math.random(1,self.SpawnRandomizeRouteHeight) end else SpawnTemplate.route.points[t].alt=nil end self:T('SpawnTemplate.route.points['..t..'].x = '..SpawnTemplate.route.points[t].x..', SpawnTemplate.route.points['..t..'].y = '..SpawnTemplate.route.points[t].y) end end self:_RandomizeZones(SpawnIndex) return self end function SPAWN:_RandomizeTemplate(SpawnIndex) self:F({self.SpawnTemplatePrefix,SpawnIndex,self.SpawnRandomizeTemplate}) if self.SpawnRandomizeTemplate then self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix=self.SpawnTemplatePrefixTable[math.random(1,#self.SpawnTemplatePrefixTable)] self.SpawnGroups[SpawnIndex].SpawnTemplate=self:_Prepare(self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix,SpawnIndex) self.SpawnGroups[SpawnIndex].SpawnTemplate.route=UTILS.DeepCopy(self.SpawnTemplate.route) self.SpawnGroups[SpawnIndex].SpawnTemplate.x=self.SpawnTemplate.x self.SpawnGroups[SpawnIndex].SpawnTemplate.y=self.SpawnTemplate.y self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time=self.SpawnTemplate.start_time local OldX=self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].x local OldY=self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y for UnitID=1,#self.SpawnGroups[SpawnIndex].SpawnTemplate.units do self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading=self.SpawnTemplate.units[1].heading self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x=self.SpawnTemplate.units[1].x+(self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x-OldX) self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y=self.SpawnTemplate.units[1].y+(self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y-OldY) self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt=self.SpawnTemplate.units[1].alt end end self:_RandomizeRoute(SpawnIndex) return self end function SPAWN:_SetInitialPosition(SpawnIndex) self:T({self.SpawnTemplatePrefix,SpawnIndex,self.SpawnRandomizeZones}) if self.SpawnFromNewPosition then self:T("Preparing Spawn at Vec2 ",self.SpawnInitPosition) local SpawnVec2=self.SpawnInitPosition self:T({SpawnVec2=SpawnVec2}) local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate 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 self:T({Route=SpawnTemplate.route}) for UnitID=1,#SpawnTemplate.units do local UnitTemplate=SpawnTemplate.units[UnitID] self:T('Before Translation SpawnTemplate.units['..UnitID..'].x = '..UnitTemplate.x..', SpawnTemplate.units['..UnitID..'].y = '..UnitTemplate.y) local SX=UnitTemplate.x local SY=UnitTemplate.y local BX=SpawnTemplate.route.points[1].x local BY=SpawnTemplate.route.points[1].y local TX=SpawnVec2.x+(SX-BX) local TY=SpawnVec2.y+(SY-BY) UnitTemplate.x=TX UnitTemplate.y=TY self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..UnitTemplate.x..', SpawnTemplate.units['..UnitID..'].y = '..UnitTemplate.y) end SpawnTemplate.route.points[1].x=SpawnVec2.x SpawnTemplate.route.points[1].y=SpawnVec2.y SpawnTemplate.x=SpawnVec2.x SpawnTemplate.y=SpawnVec2.y end return self end function SPAWN:_RandomizeZones(SpawnIndex) self:F({self.SpawnTemplatePrefix,SpawnIndex,self.SpawnRandomizeZones}) if self.SpawnRandomizeZones then local SpawnZone=nil while not SpawnZone do self:T({SpawnZoneTableCount=#self.SpawnZoneTable,self.SpawnZoneTable}) local ZoneID=math.random(#self.SpawnZoneTable) self:T(ZoneID) SpawnZone=self.SpawnZoneTable[ZoneID]:GetZoneMaybe() end self:T("Preparing Spawn in Zone",SpawnZone:GetName()) local SpawnVec2=SpawnZone:GetRandomVec2() self:T({SpawnVec2=SpawnVec2}) local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate self.SpawnGroups[SpawnIndex].SpawnZone=SpawnZone self:T({Route=SpawnTemplate.route}) for UnitID=1,#SpawnTemplate.units do local UnitTemplate=SpawnTemplate.units[UnitID] self:T('Before Translation SpawnTemplate.units['..UnitID..'].x = '..UnitTemplate.x..', SpawnTemplate.units['..UnitID..'].y = '..UnitTemplate.y) local SX=UnitTemplate.x local SY=UnitTemplate.y local BX=SpawnTemplate.route.points[1].x local BY=SpawnTemplate.route.points[1].y local TX=SpawnVec2.x+(SX-BX) local TY=SpawnVec2.y+(SY-BY) UnitTemplate.x=TX UnitTemplate.y=TY self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..UnitTemplate.x..', SpawnTemplate.units['..UnitID..'].y = '..UnitTemplate.y) end SpawnTemplate.x=SpawnVec2.x SpawnTemplate.y=SpawnVec2.y SpawnTemplate.route.points[1].x=SpawnVec2.x SpawnTemplate.route.points[1].y=SpawnVec2.y end return self end function SPAWN:_TranslateRotate(SpawnIndex,SpawnRootX,SpawnRootY,SpawnX,SpawnY,SpawnAngle) self:F({self.SpawnTemplatePrefix,SpawnIndex,SpawnRootX,SpawnRootY,SpawnX,SpawnY,SpawnAngle}) local TranslatedX=SpawnX local TranslatedY=SpawnY local RotatedX=-TranslatedX*math.cos(math.rad(SpawnAngle))+TranslatedY*math.sin(math.rad(SpawnAngle)) local RotatedY=TranslatedX*math.sin(math.rad(SpawnAngle))+TranslatedY*math.cos(math.rad(SpawnAngle)) self.SpawnGroups[SpawnIndex].SpawnTemplate.x=SpawnRootX-RotatedX self.SpawnGroups[SpawnIndex].SpawnTemplate.y=SpawnRootY+RotatedY local SpawnUnitCount=table.getn(self.SpawnGroups[SpawnIndex].SpawnTemplate.units) for u=1,SpawnUnitCount do local TranslatedX=SpawnX local TranslatedY=SpawnY-10*(u-1) local RotatedX=-TranslatedX*math.cos(math.rad(SpawnAngle))+TranslatedY*math.sin(math.rad(SpawnAngle)) local RotatedY=TranslatedX*math.sin(math.rad(SpawnAngle))+TranslatedY*math.cos(math.rad(SpawnAngle)) self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x=SpawnRootX-RotatedX self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y=SpawnRootY+RotatedY self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading=self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading+math.rad(SpawnAngle) end return self end function SPAWN:_GetSpawnIndex(SpawnIndex) self:F2({self.SpawnTemplatePrefix,SpawnIndex,self.SpawnMaxGroups,self.SpawnMaxUnitsAlive,self.AliveUnits,#self.SpawnTemplate.units}) if(self.SpawnMaxGroups==0)or(SpawnIndex<=self.SpawnMaxGroups)then if(self.SpawnMaxUnitsAlive==0)or(self.AliveUnits+#self.SpawnTemplate.units<=self.SpawnMaxUnitsAlive)or self.UnControlled==true then self:F({SpawnCount=self.SpawnCount,SpawnIndex=SpawnIndex}) if SpawnIndex and SpawnIndex>=self.SpawnCount+1 then self.SpawnCount=self.SpawnCount+1 SpawnIndex=self.SpawnCount end self.SpawnIndex=SpawnIndex if not self.SpawnGroups[self.SpawnIndex]then self:_InitializeSpawnGroups(self.SpawnIndex) end else return nil end else return nil end return self.SpawnIndex end function SPAWN:_OnBirth(EventData) self:F(self.SpawnTemplatePrefix) local SpawnGroup=EventData.IniGroup if SpawnGroup then local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) if EventPrefix then self:T({"Birth Event:",EventPrefix,self.SpawnTemplatePrefix}) if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then self.AliveUnits=self.AliveUnits+1 self:T("Alive Units: "..self.AliveUnits) end end end end function SPAWN:_OnDeadOrCrash(EventData) self:F(self.SpawnTemplatePrefix) local unit=UNIT:FindByName(EventData.IniUnitName) if unit then local EventPrefix=self:_GetPrefixFromGroupName(unit.GroupName) if EventPrefix then self:T({"Dead event: "..EventPrefix}) if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then self.AliveUnits=self.AliveUnits-1 self:T("Alive Units: "..self.AliveUnits) end end end end function SPAWN:_OnTakeOff(EventData) self:F(self.SpawnTemplatePrefix) local SpawnGroup=EventData.IniGroup if SpawnGroup then local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) if EventPrefix then self:T({"TakeOff event: "..EventPrefix}) if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then self:T("self.Landed = false") SpawnGroup:SetState(SpawnGroup,"Spawn_Landed",false) end end end end function SPAWN:_OnLand(EventData) self:F(self.SpawnTemplatePrefix) local SpawnGroup=EventData.IniGroup if SpawnGroup then local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) if EventPrefix then self:T({"Land event: "..EventPrefix}) if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then SpawnGroup:SetState(SpawnGroup,"Spawn_Landed",true) if self.RepeatOnLanding then local SpawnGroupIndex=self:GetSpawnIndexFromGroup(SpawnGroup) self:T({"Landed:","ReSpawn:",SpawnGroup:GetName(),SpawnGroupIndex}) SCHEDULER:New(nil,self.ReSpawn,{self,SpawnGroupIndex},3) end end end end end function SPAWN:_OnEngineShutDown(EventData) self:F(self.SpawnTemplatePrefix) local SpawnGroup=EventData.IniGroup if SpawnGroup then local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) if EventPrefix then self:T({"EngineShutdown event: "..EventPrefix}) if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then local Landed=SpawnGroup:GetState(SpawnGroup,"Spawn_Landed") if Landed and self.RepeatOnEngineShutDown then local SpawnGroupIndex=self:GetSpawnIndexFromGroup(SpawnGroup) self:T({"EngineShutDown: ","ReSpawn:",SpawnGroup:GetName(),SpawnGroupIndex}) SCHEDULER:New(nil,self.ReSpawn,{self,SpawnGroupIndex},3) end end end end end function SPAWN:_Scheduler() self:F2({"_Scheduler",self.SpawnTemplatePrefix,self.SpawnAliasPrefix,self.SpawnIndex,self.SpawnMaxGroups,self.SpawnMaxUnitsAlive}) self:Spawn() return true end function SPAWN:_SpawnCleanUpScheduler() self:F({"CleanUp Scheduler:",self.SpawnTemplatePrefix}) local SpawnGroup,SpawnCursor=self:GetFirstAliveGroup() self:T({"CleanUp Scheduler:",SpawnGroup,SpawnCursor}) local IsHelo=false while SpawnGroup do IsHelo=SpawnGroup:IsHelicopter() local SpawnUnits=SpawnGroup:GetUnits() for UnitID,UnitData in pairs(SpawnUnits)do local SpawnUnit=UnitData local SpawnUnitName=SpawnUnit:GetName() self.SpawnCleanUpTimeStamps[SpawnUnitName]=self.SpawnCleanUpTimeStamps[SpawnUnitName]or{} local Stamp=self.SpawnCleanUpTimeStamps[SpawnUnitName] self:T({SpawnUnitName,Stamp}) if Stamp.Vec2 then if(SpawnUnit:InAir()==false and SpawnUnit:GetVelocityKMH()<1)or IsHelo then local NewVec2=SpawnUnit:GetVec2()or{x=0,y=0} if(Stamp.Vec2.x==NewVec2.x and Stamp.Vec2.y==NewVec2.y)or(SpawnUnit:GetLife()<=1)then if Stamp.Time+self.SpawnCleanUpInterval0 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.3", 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 local coalition=Event.MarkCoalition if Event.id==world.event.S_EVENT_MARK_ADDED then self:T({event="S_EVENT_MARK_ADDED",carrier=Event.IniGroupName,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,Event.idx,coalition) end end elseif Event.id==world.event.S_EVENT_MARK_CHANGE then self:T({event="S_EVENT_MARK_CHANGE",carrier=Event.IniGroupName,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,coalition) end end elseif Event.id==world.event.S_EVENT_MARK_REMOVED then self:T({event="S_EVENT_MARK_REMOVED",carrier=Event.IniGroupName,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,MarkerID,CoalitionNumber) self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) end function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber) 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.2", name=nil, path=nil, group=nil, client=nil, GroupID=nil, Children={}, Once=false, Generic=false, debug=false, Controller=nil, groupname=nil, active=false, } 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 and self.active==false 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) self.active=true else self.path=missionCommands.addSubMenuForGroup(self.GroupID,Text,self.parentpath) self.active=true 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 self.active=false 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.5a", 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 knownunits={} 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 playerunit=client:GetName() local playergroup=client:GetGroup() local playername=client:GetPlayerName()or"none" if not knownunits[playerunit]then knownunits[playerunit]=true else self:I("Player in multi seat unit: "..playername) break end 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 local knownunits={} 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() local unitname=client:GetName() if not knownunits[unitname]then knownunits[unitname]=true else self:I("Player in multi seat unit: "..playername) break end 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,2) 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}) if not self.WayPoints then self:WayPointInitialize() end 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:SetOptionWaypointPassReport(OnOff) self:F2({self.ControllableName}) local onoff=(OnOff==nil or OnOff==true)and false or true if self:IsAir()then self:SetOption(AI.Option.Air.id.PROHIBIT_WP_PASS_REPORT,onoff) end return self end function CONTROLLABLE:SetOptionRadioSilence(OnOff) local onoff=(OnOff==true or OnOff==nil)and true or false self:F2({self.ControllableName}) if self:IsAir()then self:SetOption(AI.Option.Air.id.SILENCE,onoff) end return self end function CONTROLLABLE:SetOptionRadioContact(Objects) self:F2({self.ControllableName}) if not Objects then Objects={"Air"}end if type(Objects)~="table"then Objects={Objects}end if self:IsAir()then self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_CONTACT,Objects) end return self end function CONTROLLABLE:SetOptionRadioEngage(Objects) self:F2({self.ControllableName}) if not Objects then Objects={"Air"}end if type(Objects)~="table"then Objects={Objects}end if self:IsAir()then self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_ENGAGE,Objects) end return self end function CONTROLLABLE:SetOptionRadioKill(Objects) self:F2({self.ControllableName}) if not Objects then Objects={"Air"}end if type(Objects)~="table"then Objects={Objects}end if self:IsAir()then self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_KILL,Objects) 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 self:T2(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!",tostring(self.GroupName))) 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 and DCSGroup:isExist()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 and FirstUnit:IsAlive()then local FirstUnitCoordinate=FirstUnit:GetCoordinate() if FirstUnitCoordinate then local Heading=self:GetHeading() FirstUnitCoordinate.Heading=Heading return FirstUnitCoordinate end end end local DCSGroup=Group.getByName(self.GroupName) local DCSUnits=DCSGroup:getUnits()or{} for _,_unit in pairs(DCSUnits)do if Object.isExist(_unit)then local position=_unit:getPosition() local point=position.p~=nil and position.p or _unit:GetPoint() if point then local coord=COORDINATE:NewFromVec3(point) return coord 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) return self:CommandSetInvisible(switch) end function GROUP:CommandSetInvisible(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) return self:CommandSetImmortal(switch) end function GROUP:CommandSetImmortal(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["Interceptors"]then ThreatLevel=9 elseif Attributes["Battleplanes"]then ThreatLevel=8 elseif Attributes["Battle airplanes"]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 function STATIC:FindByMatching(Pattern) local GroupFound=nil for name,static in pairs(_DATABASE.STATICS)do if string.match(name,Pattern)then GroupFound=static break end end return GroupFound end function STATIC:FindAllByMatching(Pattern) local GroupsFound={} for name,static in pairs(_DATABASE.STATICS)do if string.match(name,Pattern)then GroupsFound[#GroupsFound+1]=static end end return GroupsFound end AIRBASE={ ClassName="AIRBASE", CategoryName={ [Airbase.Category.AIRDROME]="Airdrome", [Airbase.Category.HELIPAD]="Helipad", [Airbase.Category.SHIP]="Ship", }, activerwyno=nil, } AIRBASE.Caucasus={ ["Anapa_Vityazevo"]="Anapa-Vityazevo", ["Batumi"]="Batumi", ["Beslan"]="Beslan", ["Gelendzhik"]="Gelendzhik", ["Gudauta"]="Gudauta", ["Kobuleti"]="Kobuleti", ["Krasnodar_Center"]="Krasnodar-Center", ["Krasnodar_Pashkovsky"]="Krasnodar-Pashkovsky", ["Krymsk"]="Krymsk", ["Kutaisi"]="Kutaisi", ["Maykop_Khanskaya"]="Maykop-Khanskaya", ["Mineralnye_Vody"]="Mineralnye Vody", ["Mozdok"]="Mozdok", ["Nalchik"]="Nalchik", ["Novorossiysk"]="Novorossiysk", ["Senaki_Kolkhi"]="Senaki-Kolkhi", ["Sochi_Adler"]="Sochi-Adler", ["Soganlug"]="Soganlug", ["Sukhumi_Babushara"]="Sukhumi-Babushara", ["Tbilisi_Lochini"]="Tbilisi-Lochini", ["Vaziani"]="Vaziani", } AIRBASE.Nevada={ ["Beatty"]="Beatty", ["Boulder_City"]="Boulder City", ["Creech"]="Creech", ["Echo_Bay"]="Echo Bay", ["Groom_Lake"]="Groom Lake", ["Henderson_Executive"]="Henderson Executive", ["Jean"]="Jean", ["Laughlin"]="Laughlin", ["Lincoln_County"]="Lincoln County", ["McCarran_International"]="McCarran International", ["Mesquite"]="Mesquite", ["Mina"]="Mina", ["Nellis"]="Nellis", ["North_Las_Vegas"]="North Las Vegas", ["Pahute_Mesa"]="Pahute Mesa", ["Tonopah"]="Tonopah", ["Tonopah_Test_Range"]="Tonopah Test Range", } AIRBASE.Normandy={ ["Abbeville_Drucat"]="Abbeville Drucat", ["Amiens_Glisy"]="Amiens-Glisy", ["Argentan"]="Argentan", ["Avranches_Le_Val_Saint_Pere"]="Avranches Le Val-Saint-Pere", ["Azeville"]="Azeville", ["Barville"]="Barville", ["Bazenville"]="Bazenville", ["Beaumont_le_Roger"]="Beaumont-le-Roger", ["Beauvais_Tille"]="Beauvais-Tille", ["Beny_sur_Mer"]="Beny-sur-Mer", ["Bernay_Saint_Martin"]="Bernay Saint Martin", ["Beuzeville"]="Beuzeville", ["Biggin_Hill"]="Biggin Hill", ["Biniville"]="Biniville", ["Broglie"]="Broglie", ["Brucheville"]="Brucheville", ["Cardonville"]="Cardonville", ["Carpiquet"]="Carpiquet", ["Chailey"]="Chailey", ["Chippelle"]="Chippelle", ["Conches"]="Conches", ["Cormeilles_en_Vexin"]="Cormeilles-en-Vexin", ["Creil"]="Creil", ["Cretteville"]="Cretteville", ["Cricqueville_en_Bessin"]="Cricqueville-en-Bessin", ["Deanland"]="Deanland", ["Deauville"]="Deauville", ["Detling"]="Detling", ["Deux_Jumeaux"]="Deux Jumeaux", ["Dinan_Trelivan"]="Dinan-Trelivan", ["Dunkirk_Mardyck"]="Dunkirk-Mardyck", ["Essay"]="Essay", ["Evreux"]="Evreux", ["Farnborough"]="Farnborough", ["Fecamp_Benouville"]="Fecamp-Benouville", ["Flers"]="Flers", ["Ford"]="Ford", ["Friston"]="Friston", ["Funtington"]="Funtington", ["Goulet"]="Goulet", ["Gravesend"]="Gravesend", ["Guyancourt"]="Guyancourt", ["Hauterive"]="Hauterive", ["Heathrow"]="Heathrow", ["High_Halden"]="High Halden", ["Kenley"]="Kenley", ["Lantheuil"]="Lantheuil", ["Le_Molay"]="Le Molay", ["Lessay"]="Lessay", ["Lignerolles"]="Lignerolles", ["Longues_sur_Mer"]="Longues-sur-Mer", ["Lonrai"]="Lonrai", ["Lymington"]="Lymington", ["Lympne"]="Lympne", ["Manston"]="Manston", ["Maupertus"]="Maupertus", ["Meautis"]="Meautis", ["Merville_Calonne"]="Merville Calonne", ["Needs_Oar_Point"]="Needs Oar Point", ["Odiham"]="Odiham", ["Orly"]="Orly", ["Picauville"]="Picauville", ["Poix"]="Poix", ["Ronai"]="Ronai", ["Rouen_Boos"]="Rouen-Boos", ["Rucqueville"]="Rucqueville", ["Saint_Andre_de_lEure"]="Saint-Andre-de-lEure", ["Saint_Aubin"]="Saint-Aubin", ["Saint_Omer_Wizernes"]="Saint-Omer Wizernes", ["Saint_Pierre_du_Mont"]="Saint Pierre du Mont", ["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer", ["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer", ["Sommervieu"]="Sommervieu", ["Stoney_Cross"]="Stoney Cross", ["Tangmere"]="Tangmere", ["Triqueville"]="Triqueville", ["Villacoublay"]="Villacoublay", ["Vrigny"]="Vrigny", ["West_Malling"]="West Malling", } AIRBASE.PersianGulf={ ["Abu_Dhabi_Intl"]="Abu Dhabi Intl", ["Abu_Musa_Island"]="Abu Musa Island", ["Al_Ain_Intl"]="Al Ain Intl", ["Al_Bateen"]="Al-Bateen", ["Al_Dhafra_AFB"]="Al Dhafra AFB", ["Al_Maktoum_Intl"]="Al Maktoum Intl", ["Al_Minhad_AFB"]="Al Minhad AFB", ["Bandar_Abbas_Intl"]="Bandar Abbas Intl", ["Bandar_Lengeh"]="Bandar Lengeh", ["Bandar_e_Jask"]="Bandar-e-Jask", ["Dubai_Intl"]="Dubai Intl", ["Fujairah_Intl"]="Fujairah Intl", ["Havadarya"]="Havadarya", ["Jiroft"]="Jiroft", ["Kerman"]="Kerman", ["Khasab"]="Khasab", ["Kish_Intl"]="Kish Intl", ["Lar"]="Lar", ["Lavan_Island"]="Lavan Island", ["Liwa_AFB"]="Liwa AFB", ["Qeshm_Island"]="Qeshm Island", ["Quasoura_airport"]="Quasoura_airport", ["Ras_Al_Khaimah_Intl"]="Ras Al Khaimah Intl", ["Sas_Al_Nakheel"]="Sas Al Nakheel", ["Sharjah_Intl"]="Sharjah Intl", ["Shiraz_Intl"]="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", ["Biggin_Hill"]="Biggin Hill", ["Detling"]="Detling", ["Dunkirk_Mardyck"]="Dunkirk Mardyck", ["Eastchurch"]="Eastchurch", ["Hawkinge"]="Hawkinge", ["Headcorn"]="Headcorn", ["High_Halden"]="High Halden", ["Lympne"]="Lympne", ["Manston"]="Manston", ["Merville_Calonne"]="Merville Calonne", ["Saint_Omer_Longuenesse"]="Saint Omer Longuenesse", } AIRBASE.Syria={ ["Abu_al_Duhur"]="Abu al-Duhur", ["Adana_Sakirpasa"]="Adana Sakirpasa", ["Akrotiri"]="Akrotiri", ["Al_Dumayr"]="Al-Dumayr", ["Al_Qusayr"]="Al Qusayr", ["Aleppo"]="Aleppo", ["Amman"]="Amman", ["An_Nasiriyah"]="An Nasiriyah", ["At_Tanf"]="At Tanf", ["Bassel_Al_Assad"]="Bassel Al-Assad", ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", ["Damascus"]="Damascus", ["Deir_ez_Zor"]="Deir ez-Zor", ["Ercan"]="Ercan", ["Eyn_Shemer"]="Eyn Shemer", ["Gaziantep"]="Gaziantep", ["Gazipasa"]="Gazipasa", ["Gecitkale"]="Gecitkale", ["H3"]="H3", ["H3_Northwest"]="H3 Northwest", ["H3_Southwest"]="H3 Southwest", ["H4"]="H4", ["Haifa"]="Haifa", ["Hama"]="Hama", ["Hatay"]="Hatay", ["Herzliya"]="Herzliya", ["Incirlik"]="Incirlik", ["Jirah"]="Jirah", ["Khalkhalah"]="Khalkhalah", ["Kharab_Ishk"]="Kharab Ishk", ["King_Abdullah_II"]="King Abdullah II", ["King_Hussein_Air_College"]="King Hussein Air College", ["Kingsfield"]="Kingsfield", ["Kiryat_Shmona"]="Kiryat Shmona", ["Kuweires"]="Kuweires", ["Lakatamia"]="Lakatamia", ["Larnaca"]="Larnaca", ["Marj_Ruhayyil"]="Marj Ruhayyil", ["Marj_as_Sultan_North"]="Marj as Sultan North", ["Marj_as_Sultan_South"]="Marj as Sultan South", ["Megiddo"]="Megiddo", ["Mezzeh"]="Mezzeh", ["Minakh"]="Minakh", ["Muwaffaq_Salti"]="Muwaffaq Salti", ["Naqoura"]="Naqoura", ["Nicosia"]="Nicosia", ["Palmyra"]="Palmyra", ["Paphos"]="Paphos", ["Pinarbashi"]="Pinarbashi", ["Prince_Hassan"]="Prince Hassan", ["Qabr_as_Sitt"]="Qabr as Sitt", ["Ramat_David"]="Ramat David", ["Rayak"]="Rayak", ["Rene_Mouawad"]="Rene Mouawad", ["Rosh_Pina"]="Rosh Pina", ["Ruwayshid"]="Ruwayshid", ["Sanliurfa"]="Sanliurfa", ["Sayqal"]="Sayqal", ["Shayrat"]="Shayrat", ["Tabqa"]="Tabqa", ["Taftanaz"]="Taftanaz", ["Tal_Siman"]="Tal Siman", ["Tha_lah"]="Tha'lah", ["Tiyas"]="Tiyas", ["Wujah_Al_Hajar"]="Wujah Al Hajar", } AIRBASE.MarianaIslands={ ["Andersen_AFB"]="Andersen AFB", ["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", ["North_West_Field"]="North West Field", ["Olf_Orote"]="Olf Orote", ["Pagan_Airstrip"]="Pagan Airstrip", ["Rota_Intl"]="Rota Intl", ["Saipan_Intl"]="Saipan Intl", ["Tinian_Intl"]="Tinian Intl", } AIRBASE.SouthAtlantic={ ["Almirante_Schroeders"]="Almirante Schroeders", ["Caleta_Tortel"]="Caleta Tortel", ["Comandante_Luis_Piedrabuena"]="Comandante Luis Piedrabuena", ["Cullen"]="Cullen", ["El_Calafate"]="El Calafate", ["Franco_Bianco"]="Franco Bianco", ["Gobernador_Gregores"]="Gobernador Gregores", ["Goose_Green"]="Goose Green", ["Gull_Point"]="Gull Point", ["Hipico_Flying_Club"]="Hipico Flying Club", ["Mount_Pleasant"]="Mount Pleasant", ["O_Higgins"]="O'Higgins", ["Pampa_Guanaco"]="Pampa Guanaco", ["Port_Stanley"]="Port Stanley", ["Porvenir"]="Porvenir", ["Puerto_Natales"]="Puerto Natales", ["Puerto_Santa_Cruz"]="Puerto Santa Cruz", ["Puerto_Williams"]="Puerto Williams", ["Punta_Arenas"]="Punta Arenas", ["Rio_Chico"]="Rio Chico", ["Rio_Gallegos"]="Rio Gallegos", ["Rio_Grande"]="Rio Grande", ["Rio_Turbio"]="Rio Turbio", ["San_Carlos_FOB"]="San Carlos FOB", ["San_Julian"]="San Julian", ["Tolhuin"]="Tolhuin", ["Ushuaia"]="Ushuaia", ["Ushuaia_Helo_Port"]="Ushuaia Helo Port", } AIRBASE.Sinai={ ["Abu_Rudeis"]="Abu Rudeis", ["Abu_Suwayr"]="Abu Suwayr", ["Al_Ismailiyah"]="Al Ismailiyah", ["Al_Mansurah"]="Al Mansurah", ["As_Salihiyah"]="As Salihiyah", ["AzZaqaziq"]="AzZaqaziq", ["Baluza"]="Baluza", ["Ben_Gurion"]="Ben-Gurion", ["Bilbeis_Air_Base"]="Bilbeis Air Base", ["Bir_Hasanah"]="Bir Hasanah", ["Cairo_International_Airport"]="Cairo International Airport", ["Cairo_West"]="Cairo West", ["Difarsuwar_Airfield"]="Difarsuwar Airfield", ["El_Arish"]="El Arish", ["El_Gora"]="El Gora", ["Fayed"]="Fayed", ["Hatzerim"]="Hatzerim", ["Hatzor"]="Hatzor", ["Inshas_Airbase"]="Inshas Airbase", ["Kedem"]="Kedem", ["Kibrit_Air_Base"]="Kibrit Air Base", ["Melez"]="Melez", ["Nevatim"]="Nevatim", ["Ovda"]="Ovda", ["Palmahim"]="Palmahim", ["Ramon_Airbase"]="Ramon Airbase", ["Sde_Dov"]="Sde Dov", ["St_Catherine"]="St Catherine", ["Tel_Nof"]="Tel Nof", ["Wadi_al_Jandali"]="Wadi al Jandali", } AIRBASE.Kola={ ["Bas_100"]="Bas 100", ["Bodo"]="Bodo", ["Jokkmokk"]="Jokkmokk", ["Kalixfors"]="Kalixfors", ["Kemi_Tornio"]="Kemi Tornio", ["Kiruna"]="Kiruna", ["Lakselv"]="Lakselv", ["Monchegorsk"]="Monchegorsk", ["Murmansk_International"]="Murmansk International", ["Olenegorsk"]="Olenegorsk", ["Rovaniemi"]="Rovaniemi", ["Severomorsk1"]="Severomorsk1", ["Severomorsk3"]="Severomorsk3", } AIRBASE.TerminalType={ Runway=16, HelicopterOnly=40, Shelter=68, OpenMed=72, SmallSizeFighter=100, 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)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 or Term_Type==AIRBASE.TerminalType.SmallSizeFighter 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 if self.desc.guidance then self.guidance=self.desc.guidance end 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:IsFoxOne() return self.guidance==Weapon.GuidanceType.RADAR_SEMI_ACTIVE end function WEAPON:IsFoxTwo() return self.guidance==Weapon.GuidanceType.IR end function WEAPON:IsFoxThree() return self.guidance==Weapon.GuidanceType.RADAR_ACTIVE 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.Json2Lua(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.18.4" } 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,SavePath,AutoSave) 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.AutoSavePath=SavePath self.AutoSave=AutoSave or 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:AddScoreSetGroup(Set,Score) local set=Set:GetSetObjects() for _,_group in pairs(set)do if _group and _group:IsAlive()then self:AddScoreGroup(_group,Score) end end local function AddScore(group) self:AddScoreGroup(group,Score) end function Set:OnAfterAdded(From,Event,To,ObjectName,Object) AddScore(Object) 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 or PlayerHit.ThreatType==""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 if Player.HitPlayers[TargetPlayerName]then self:OnKillPvP(PlayerName,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(PlayerName,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(PlayerName,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(PlayerName,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==true then if ScoringCSV then self.ScoringCSV=ScoringCSV local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] local fdir=path..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(PlayerName,TargetPlayerName,IsTeamKill,TargetThreatLevel,PlayerThreatLevel,Score) end function SCORING:OnKillPvE(PlayerName,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 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 then self:T2(self.lid..string.format("Stopping RAT in %d sec!",delay)) self:ScheduleOnce(delay,RAT.Stop,self) else self:T(self.lid.."Stopping RAT: Clearing schedulers and unhandling events!") if self.sid_Activate then self.Scheduler:ScheduleStop(self.sid_Activate) end if self.sid_Spawn then self.Scheduler:ScheduleStop(self.sid_Spawn) end if self.sid_Status then self.Scheduler:ScheduleStop(self.sid_Status) end if self.Scheduler then self.Scheduler:Clear() end self.norespawn=true self:UnHandleEvent(EVENTS.Birth) self:UnHandleEvent(EVENTS.EngineStartup) self:UnHandleEvent(EVENTS.Takeoff) self:UnHandleEvent(EVENTS.Land) self:UnHandleEvent(EVENTS.EngineShutdown) self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.Crash) self:UnHandleEvent(EVENTS.Hit) end end function RAT:Spawn(naircraft) if self.spawninitialized==true then self:E("ERROR: Spawn function should only be called once per RAT object! Exiting and returning nil.") return nil else self.spawninitialized=true end self.ngroups=naircraft or 1 if self.ATCswitch and not RAT.ATC.init then RAT._ATCInit(self.airports_map) end if self.f10menu and not RAT.MenuF10 then RAT.MenuF10=MENU_MISSION:New("RAT") end self:_SetCoalitionTable() self:_GetAirportsOfCoalition() if not self.SubMenuName then self.SubMenuName=self.alias end if self.departure_Azone~=nil then self.departure_ports=self:_GetAirportsInZone(self.departure_Azone) end if self.destination_Azone~=nil then self.destination_ports=self:_GetAirportsInZone(self.destination_Azone) end if self.addfriendlydepartures then self:_AddFriendlyAirports(self.departure_ports) end if self.addfriendlydestinations then self:_AddFriendlyAirports(self.destination_ports) end if self.FLcruise==nil then if self.category==RAT.cat.plane then self.FLcruise=200*RAT.unit.FL2m else self.FLcruise=005*RAT.unit.FL2m end end if self.category==RAT.cat.heli then self.mindist=50 end self:_CheckConsistency() local text=string.format("\n******************************************************\n") text=text..string.format("Spawning %i aircraft from template %s of type %s.\n",self.ngroups,self.SpawnTemplatePrefix,self.aircraft.type) text=text..string.format("Alias: %s\n",self.alias) text=text..string.format("Category: %s\n",self.category) text=text..string.format("Friendly coalitions: %s\n",self.friendly) text=text..string.format("Number of airports on map : %i\n",#self.airports_map) text=text..string.format("Number of friendly airports: %i\n",#self.airports) text=text..string.format("Totally random departure: %s\n",tostring(self.random_departure)) if not self.random_departure then text=text..string.format("Number of departure airports: %d\n",self.Ndeparture_Airports) text=text..string.format("Number of departure zones : %d\n",self.Ndeparture_Zones) end text=text..string.format("Totally random destination: %s\n",tostring(self.random_destination)) if not self.random_destination then text=text..string.format("Number of destination airports: %d\n",self.Ndestination_Airports) text=text..string.format("Number of destination zones : %d\n",self.Ndestination_Zones) end text=text..string.format("Min dist to destination: %4.1f\n",self.mindist) text=text..string.format("Max dist to destination: %4.1f\n",self.maxdist) text=text..string.format("Terminal type: %s\n",tostring(self.termtype)) text=text..string.format("Takeoff type: %i\n",self.takeoff) text=text..string.format("Landing type: %i\n",self.landing) text=text..string.format("Commute: %s\n",tostring(self.commute)) text=text..string.format("Journey: %s\n",tostring(self.continuejourney)) text=text..string.format("Destination Zone: %s\n",tostring(self.destinationzone)) text=text..string.format("Return Zone: %s\n",tostring(self.returnzone)) text=text..string.format("Spawn delay: %4.1f\n",self.spawndelay) text=text..string.format("Spawn interval: %4.1f\n",self.spawninterval) text=text..string.format("Respawn delay: %s\n",tostring(self.respawn_delay)) text=text..string.format("Respawn off: %s\n",tostring(self.norespawn)) text=text..string.format("Respawn after landing: %s\n",tostring(self.respawn_at_landing)) text=text..string.format("Respawn after take-off: %s\n",tostring(self.respawn_after_takeoff)) text=text..string.format("Respawn after crash: %s\n",tostring(self.respawn_after_crash)) text=text..string.format("Respawn in air: %s\n",tostring(self.respawn_inair)) text=text..string.format("ROE: %s\n",tostring(self.roe)) text=text..string.format("ROT: %s\n",tostring(self.rot)) text=text..string.format("Immortal: %s\n",tostring(self.immortal)) text=text..string.format("Invisible: %s\n",tostring(self.invisible)) text=text..string.format("Vclimb: %4.1f\n",self.Vclimb) text=text..string.format("AlphaDescent: %4.2f\n",self.AlphaDescent) text=text..string.format("Vcruisemax: %s\n",tostring(self.Vcruisemax)) text=text..string.format("FLcruise = %6.1f km = FL%3.0f\n",self.FLcruise/1000,self.FLcruise/RAT.unit.FL2m) text=text..string.format("FLuser: %s\n",tostring(self.Fluser)) text=text..string.format("FLminuser: %s\n",tostring(self.FLminuser)) text=text..string.format("FLmaxuser: %s\n",tostring(self.FLmaxuser)) text=text..string.format("Place markers: %s\n",tostring(self.placemarkers)) text=text..string.format("Report status: %s\n",tostring(self.reportstatus)) text=text..string.format("Status interval: %4.1f\n",self.statusinterval) text=text..string.format("Time inactive: %4.1f\n",self.Tinactive) text=text..string.format("Create F10 menu : %s\n",tostring(self.f10menu)) text=text..string.format("F10 submenu name: %s\n",self.SubMenuName) text=text..string.format("ATC enabled : %s\n",tostring(self.ATCswitch)) text=text..string.format("Radio comms : %s\n",tostring(self.radio)) text=text..string.format("Radio frequency : %s\n",tostring(self.frequency)) text=text..string.format("Radio modulation : %s\n",tostring(self.frequency)) text=text..string.format("Tail # prefix : %s\n",tostring(self.onboardnum)) text=text..string.format("Check on runway: %s\n",tostring(self.checkonrunway)) text=text..string.format("Max respawn attempts: %s\n",tostring(self.onrunwaymaxretry)) text=text..string.format("Check on top: %s\n",tostring(self.checkontop)) text=text..string.format("Uncontrolled: %s\n",tostring(self.uncontrolled)) if self.uncontrolled and self.activate_uncontrolled then text=text..string.format("Uncontrolled max : %4.1f\n",self.activate_max) text=text..string.format("Uncontrolled delay: %4.1f\n",self.activate_delay) text=text..string.format("Uncontrolled delta: %4.1f\n",self.activate_delta) text=text..string.format("Uncontrolled frand: %4.1f\n",self.activate_frand) end if self.livery then text=text..string.format("Available liveries:\n") for _,livery in pairs(self.livery)do text=text..string.format("- %s\n",livery) end end text=text..string.format("******************************************************\n") self:T(self.lid..text) if self.f10menu then self.Menu[self.SubMenuName]=MENU_MISSION:New(self.SubMenuName,RAT.MenuF10) self.Menu[self.SubMenuName]["groups"]=MENU_MISSION:New("Groups",self.Menu[self.SubMenuName]) MENU_MISSION_COMMAND:New("Spawn new group",self.Menu[self.SubMenuName],self._SpawnWithRoute,self) MENU_MISSION_COMMAND:New("Delete markers",self.Menu[self.SubMenuName],self._DeleteMarkers,self) MENU_MISSION_COMMAND:New("Status report",self.Menu[self.SubMenuName],self.Status,self,true) end local Tstart=self.spawndelay local dt=self.spawninterval if self.takeoff==RAT.wp.runway and not self.random_departure then dt=math.max(dt,180) end local Tstop=Tstart+dt*(self.ngroups-1) self.sid_Status=self:ScheduleRepeat(Tstart+1,self.statusinterval,nil,nil,RAT.Status,self) self:HandleEvent(EVENTS.Birth,self._OnBirth) self:HandleEvent(EVENTS.EngineStartup,self._OnEngineStartup) self:HandleEvent(EVENTS.Takeoff,self._OnTakeoff) self:HandleEvent(EVENTS.Land,self._OnLand) self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutdown) self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash) self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash) self:HandleEvent(EVENTS.Hit,self._OnHit) if self.ngroups==0 then return nil end self.sid_Spawn=self:ScheduleRepeat(Tstart,dt,0.0,Tstop,RAT._SpawnWithRoute,self) if self.uncontrolled and self.activate_uncontrolled then self.sid_Activate=self:ScheduleRepeat(self.activate_delay,self.activate_delta,self.activate_frand,nil,RAT._ActivateUncontrolled,self) end return true end function RAT:_CheckConsistency() self:F2() if not self.random_departure then for _,name in pairs(self.departure_ports)do if self:_AirportExists(name)then self.Ndeparture_Airports=self.Ndeparture_Airports+1 elseif self:_ZoneExists(name)then self.Ndeparture_Zones=self.Ndeparture_Zones+1 end end if self.Ndeparture_Zones>0 and self.takeoff~=RAT.wp.air then self.takeoff=RAT.wp.air self:E(self.lid..string.format("WARNING: 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(self.lid.."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(self.lid.."WARNING: 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(self.lid.."ERROR: "..text) MESSAGE:New(text,30):ToAll() end end if self.destinationzone and self.returnzone then self:E(self.lid.."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(self.lid.."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(self.lid.."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(self.lid.."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(self.lid.."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:SetSpawnLimit(Nmax) self.NspawnMax=Nmax return self end function RAT:RespawnAfterLanding(delay) self:F2(delay) self.respawn_at_landing=true self:SetRespawnDelay(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: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(self.lid.."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(self.lid..text) end function RAT:_SpawnWithRoute(_departure,_destination,_takeoff,_landing,_livery,_waypoint,_lastpos,_nrespawn,parkingdata) self:F({rat=self.lid,departure=_departure,destination=_destination,takeoff=_takeoff,landing=_landing,livery=_livery,waypoint=_waypoint,lastpos=_lastpos,nrespawn=_nrespawn}) if self.NspawnMax and self.SpawnIndex>=self.NspawnMax then self:T(self.lid..string.format("Max limit of spawns reached %d >= %d! Will not spawn any more groups",self.NspawnMax,self.SpawnIndex)) return else self:T2(self.lid..string.format("Spawning with spawn index=%d",self.SpawnIndex)) end 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,wpdesc,wpstatus=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(self.lid..text) else livery=nil end local uncontrolled=self.uncontrolled local isFlightcontrol=self:_IsFlightControlAirbase(departure) if takeoff~=RAT.wp.air and departure and isFlightcontrol then takeoff=RAT.wp.cold uncontrolled=true end local successful=self:_ModifySpawnTemplate(waypoints,livery,_lastpos,departure,takeoff,parkingdata,uncontrolled) if not successful then return nil end local group=self:SpawnWithIndex(self.SpawnIndex) local groupname=group:GetName() local flightgroup=FLIGHTGROUP:New(group) if self.ATCswitch then flightgroup.holdtime=nil end flightgroup.stuckDespawn=false self.alive=self.alive+1 self:T(self.lid..string.format("Alive groups counter now = %d.",self.alive)) if self.ATCswitch and landing==RAT.wp.landing then local airbasename=destination:GetName() if self.returnzone then airbasename=departure:GetName() end if not self:_IsFlightControlAirbase(airbasename)then self:_ATCAddFlight(groupname,airbasename) end end if self.placemarkers then self:_PlaceMarkers(waypoints,wpdesc,self.SpawnIndex) end if isFlightcontrol and not self.activate_uncontrolled then local N=math.random(120) self:T(self.lid..string.format("Flight will be ready for takeoff in %d seconds",N)) flightgroup:SetReadyForTakeoff(true,N) end if self.invisible then flightgroup:SetDefaultInvisible(true) flightgroup:SwitchInvisible(true) end if self.immortal then flightgroup:SetDefaultImmortal(true) flightgroup:SwitchImmortal(true) end if self.eplrs then flightgroup:SetDefaultEPLRS(true) flightgroup:SwitchEPLRS(true) end self:_SetROE(flightgroup,self.roe) self:_SetROT(flightgroup,self.rot) local ratcraft={} ratcraft.index=self.SpawnIndex ratcraft.group=group ratcraft.flightgroup=flightgroup ratcraft.destination=destination ratcraft.departure=departure ratcraft.waypoints=waypoints ratcraft.airborne=group:InAir() ratcraft.nunits=group:GetInitialSize() ratcraft.Pnow=group:GetCoordinate() ratcraft.Distance=0 ratcraft.takeoff=takeoff ratcraft.landing=landing ratcraft.wpdesc=wpdesc ratcraft.wpstatus=wpstatus ratcraft.active=not uncontrolled ratcraft.status=RAT.status.Spawned ratcraft.livery=livery ratcraft.despawnme=false ratcraft.nrespawn=nrespawn self.ratcraft[self.SpawnIndex]=ratcraft 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,flightgroup,RAT.ROE.weaponhold) MENU_MISSION_COMMAND:New("Weapons free",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,flightgroup,RAT.ROE.weaponfree) MENU_MISSION_COMMAND:New("Return fire",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,flightgroup,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,flightgroup,RAT.ROT.noreaction) MENU_MISSION_COMMAND:New("Passive defense",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,flightgroup,RAT.ROT.passive) MENU_MISSION_COMMAND:New("Evade on fire",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,flightgroup,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 function flightgroup.OnAfterPassingWaypoint(Flightgroup,From,Event,To,Waypoint) local waypoint=Waypoint local flightgroup=Flightgroup local wpid=waypoint.uid local ratcraft=self:_GetRatcraftFromGroup(flightgroup.group) local wpdescription=tostring(ratcraft.wpdesc[wpid]) local wpstatus=ratcraft.wpstatus[wpid] self:T(self.lid..string.format("RAT passed waypoint %s [uid=%d]: %s [status=%s]",waypoint.name,wpid,wpdescription,wpstatus)) self:_SetStatus(group,wpstatus) if waypoint.uid==3 then end end function flightgroup.OnAfterPassedFinalWaypoint(flightgroup,From,Event,To) self:T(self.lid..string.format("RAT passed FINAL waypoint")) local ratcraft=self:_GetRatcraftFromGroup(flightgroup.group) local text=string.format("Flight %s arrived at final destination %s.",group:GetName(),destination:GetName()) MESSAGE:New(text,10):ToAllIf(self.reportstatus) self:T(self.lid..text) if landing==RAT.wp.air then local text=string.format("Activating despawn switch for flight %s! Group will be detroyed soon.",group:GetName()) MESSAGE:New(text,10):ToAllIf(self.Debug) self:T(self.lid..text) ratcraft.despawnme=true end end function flightgroup.OnAfterRTB(flightgroup,From,Event,To,airbase,SpeedTo,SpeedHold,SpeedLand) self:T(self.lid..string.format("RAT group is RTB")) end function flightgroup.OnAfterHolding(Flightgroup,From,Event,To) local flightgroup=Flightgroup local ratcraft=self:_GetRatcraftFromGroup(flightgroup.group) local destinationname=ratcraft.destination:GetName() local text=string.format("Flight %s to %s ATC: Holding and awaiting landing clearance.",groupname,destinationname) self:T(self.lid..text) MESSAGE:New(text,10):ToAllIf(self.reportstatus) local fc=_DATABASE:GetFlightControl(destinationname) if self.ATCswitch and not fc then self:T(self.lid..string.format("RAT group is HOLDING ==> ATCRegisterFlight")) if self.f10menu then MENU_MISSION_COMMAND:New("Clear for landing",self.Menu[self.SubMenuName].groups[self.SpawnIndex],flightgroup.ClearToLand,flightgroup) end RAT._ATCRegisterFlight(groupname,timer.getTime()) end end function flightgroup.OnAfterLanded(Flightgroup,From,Event,To,Airport) self:T(self.lid..string.format("RAT group landed at airbase")) end function flightgroup.OnAfterArrived(Flightgroup,From,Event,To) self:T(self.lid..string.format("RAT group arrived")) end function flightgroup.OnAfterStuck(Flightgroup,From,Event,To,Stucktime) local flightgroup=Flightgroup self:T(self.lid..string.format("Group %s got stuck for %d seconds",flightgroup:GetName(),Stucktime)) if Stucktime>10*60 then self:_Respawn(flightgroup.group) end end return self.SpawnIndex end function RAT:_IsFlightControlAirbase(airbase) if type(airbase)=="table"then airbase=airbase:GetName() end if airbase then local fc=_DATABASE:GetFlightControl(airbase) if fc then self:T(self.lid..string.format("Airbase %s has a FLIGHTCONTROL running",airbase)) return true else return false end end return nil end function RAT:ClearForLanding(name) trigger.action.setUserFlag(name,1) local flagvalue=trigger.misc.getUserFlag(name) self:T(self.lid.."ATC: User flag value (landing) for "..name.." set to "..flagvalue) end function RAT:_Respawn(group,lastpos,delay) if delay and delay>0 then self:ScheduleOnce(delay,RAT._Respawn,self,group,lastpos,0) else if group then self:T(self.lid..string.format("Respawning ratcraft from group %s",group:GetName())) else self:E(self.lid..string.format("ERROR: group is nil in _Respawn!")) return nil end local ratcraft=self:_GetRatcraftFromGroup(group) lastpos=lastpos or group:GetCoordinate() local departure=ratcraft.departure local destination=ratcraft.destination local takeoff=ratcraft.takeoff local landing=ratcraft.landing local livery=ratcraft.livery local lastwp=ratcraft.waypoints[#ratcraft.waypoints] local flightgroup=ratcraft.flightgroup local parkingdata=nil if self.continuejourney or self.commute then for _,_element in pairs(flightgroup.elements)do local element=_element if element.parking then if parkingdata==nil then parkingdata={} end self:T(self.lid..string.format("Element %s was parking at spot id=%d",element.name,element.parking.TerminalID)) table.insert(parkingdata,UTILS.DeepCopy(element.parking)) else self:E(self.lid..string.format("WARNING: Element %s did NOT have a not parking spot!",tostring(element.name))) end end end self:_Despawn(ratcraft.group) 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 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=self.respawn_delay or 1 self:T(self.lid..string.format("%s delayed respawn in %.1f seconds.",self.alias,respawndelay)) self:ScheduleOnce(respawndelay,RAT._SpawnWithRoute,self,_departure,_destination,_takeoff,_landing,_livery,nil,_lastpos,nil,parkingdata) end end function RAT:_Despawn(group,delay) if delay and delay>0 then self:ScheduleOnce(delay,RAT._Despawn,self,group,0) else if group then local index=self:GetSpawnIndexFromGroup(group) if index then self:T(self.lid..string.format("Despawning group %s (index=%d)",group:GetName(),index)) local ratcraft=self.ratcraft[index] ratcraft.flightgroup:Despawn() ratcraft.flightgroup:__Stop(0.1) self.ratcraft[index].group=nil self.ratcraft[index]["status"]="Dead" self.ratcraft[index]=nil if self.f10menu and self.SubMenuName~=nil then self.Menu[self.SubMenuName]["groups"][index]:Remove() end end end end 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(self.lid..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(self.lid..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(self.lid..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(self.lid.."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(self.lid.."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(self.lid.."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(self.lid..text) if d_cruise<0 then d_cruise=100 end local wp={} local c={} local waypointdescriptions={} local waypointstatus={} 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) waypointdescriptions[#wp]="Departure" 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) waypointdescriptions[#wp]="Begin of Cruise" 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) waypointdescriptions[#wp]="Climb" waypointstatus[#wp]=RAT.status.Climb wp[#wp+1]=self:_Waypoint(#wp+1,"Begin of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) waypointdescriptions[#wp]="Begin of Cruise" 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) waypointdescriptions[#wp]="Return Zone" 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) waypointdescriptions[#wp]="Final Destination" 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) waypointdescriptions[#wp]="End of Cruise" 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) waypointdescriptions[#wp]="End of Cruise" 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) waypointdescriptions[#wp]="Descent" 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) waypointdescriptions[#wp]="Descent" waypointstatus[#wp]=RAT.status.DescentHolding end end if landing==RAT.wp.landing then c[#c+1]=Pdestination wp[#wp+1]=self:_Waypoint(#wp+1,"Final Destination",landing,c[#wp+1],VxFinal,H_destination,destination) waypointdescriptions[#wp]="Final Destination" 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:",waypointdescriptions) if self.returnzone then return departure,destination_returnzone,waypoints,waypointdescriptions,waypointstatus else return departure,destination,waypoints,waypointdescriptions,waypointstatus 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(self.lid..string.format("ERROR! Takeoff is not in air. Cannot use %s as departure.",name)) end else self:E(self.lid..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(self.lid..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(self.lid..text) else self:E(self.lid..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(self.lid..string.format("ERROR! Landing is not in air. Cannot use zone %s as destination!",name)) end else self:E(self.lid..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(self.lid..text) end end end end end self:T(self.lid..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(self.lid..text) else self:E(self.lid.."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(self.lid..text) else self:E(self.lid..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(self.lid..text) end end function RAT:Status(message,forID) self:T(self.lid.."Checking status") local Tnow=timer.getTime() local nalive=0 for spawnindex,_ratcraft in pairs(self.ratcraft)do local ratcraft=_ratcraft self:T(self.lid..string.format("Ratcraft Index=%s",tostring(spawnindex))) local group=ratcraft.group if group and group:IsAlive()then nalive=nalive+1 self:T(self.lid..string.format("Ratcraft Index=%s is ALIVE",tostring(spawnindex))) local prefix=self:_GetPrefixFromGroup(group) local life=self:_GetLife(group) local fuel=group:GetFuel()*100.0 local airborne=group:InAir() local coords=group:GetCoordinate() local alt=coords~=nil and coords.y or 1000 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 Pnow=coords local Dtravel=Pnow:Get2DDistance(ratcraft.Pnow) ratcraft.Pnow=Pnow ratcraft.Distance=ratcraft.Distance+Dtravel local Ddestination=Pnow: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 dest = %6.1f km",Ddestination/1000) self:T(self.lid..text) if message then MESSAGE:New(text,20):ToAll() end end if ratcraft.despawnme then if self.norespawn or self.respawn_after_takeoff then if self.despawnair then self:T(self.lid..string.format("[STATUS despawnme] Flight %s will be despawned NOW and NO new group is created!",self.alias)) self:_Despawn(group) end else self:T(self.lid..string.format("[STATUS despawnme] Flight %s will be despawned NOW and a new group is respawned!",self.alias)) self:_Respawn(group) end end else local text=string.format("Group does not exist in loop ratcraft status for spawn index=%d",spawnindex) self:T2(self.lid..text) self:T2(ratcraft) end end local text=string.format("Alive groups of %s: %d, nalive=%d/%d",self.alias,self.alive,nalive,self.ngroups) self:T(self.lid..text) MESSAGE:New(text,20):ToAllIf(message and not forID) end function RAT:_RemoveRatcraft(ratcraft) self.ratcraft[ratcraft.index]=nil return self end function RAT:_GetRatcraftFromGroup(group) local index=self:GetSpawnIndexFromGroup(group) local ratcraft=self.ratcraft[index] return ratcraft 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(self.lid.."ERROR! Unit does not exist in RAT_Getlife(). Returning zero.") end else self:T2(self.lid.."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 ratcraft=self:_GetRatcraftFromGroup(group) if ratcraft then ratcraft.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(self.lid..text) if not(no1 or no2 or no3)then MESSAGE:New(text,10):ToAllIf(self.reportstatus) end end end end function RAT:_GetRatcraftFromGroup(group) if group then local index=self:GetSpawnIndexFromGroup(group) if self.ratcraft[index]then return self.ratcraft[index] end end return nil end function RAT:GetStatus(group) if group and group:IsAlive()then local ratcraft=self:_GetRatcraftFromGroup(group) if ratcraft then return ratcraft.status end end return"nonexistant" end function RAT:_OnBirth(EventData) self:F3(EventData) self:T3(self.lid.."Captured event birth!") local SpawnGroup=EventData.IniGroup if SpawnGroup then local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) if EventPrefix and EventPrefix==self.alias then local text="Event: Group "..SpawnGroup:GetName().." was born." self:T(self.lid..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 ratcraft=self.ratcraft[i] local _departure=ratcraft.departure:GetName() local _destination=ratcraft.destination:GetName() local _nrespawn=ratcraft.nrespawn local _takeoff=ratcraft.takeoff local _landing=ratcraft.landing local _livery=ratcraft.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(self.lid..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(self.lid..text) self:_Respawn(SpawnGroup,nil,3) else text="Event: Group "..SpawnGroup:GetName().." will be destroyed now" self:T(self.lid..text) self:_Despawn(SpawnGroup) end end end end else self:T2(self.lid.."ERROR: Group does not exist in RAT:_OnEngineShutdown().") end end function RAT:_OnHit(EventData) self:F3(EventData) self:T(self.lid..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(self.lid..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(self.lid.."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(self.lid..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(self.lid.."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(self.lid..text) local status=RAT.status.EventDead self:_SetStatus(SpawnGroup,status) end end else self:T2(self.lid.."ERROR: Group does not exist in RAT:_OnDead().") end end function RAT:_OnCrash(EventData) self:F3(EventData) self:T3(self.lid.."Captured event Crash!") local SpawnGroup=EventData.IniGroup if SpawnGroup then local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) if EventPrefix and EventPrefix==self.alias then local ratcraft=self:_GetRatcraftFromGroup(SpawnGroup) if ratcraft then ratcraft.nunits=ratcraft.nunits-1 local _n0=SpawnGroup:GetInitialSize() local text=string.format("Event: Group %s crashed. Unit %s. Units still alive %d of %d.",SpawnGroup:GetName(),EventData.IniUnitName,ratcraft.nunits,_n0) self:T(self.lid..text) local status=RAT.status.EventCrash self:_SetStatus(SpawnGroup,status) if ratcraft.nunits==0 and self.respawn_after_crash and not self.norespawn then self:T(self.lid..string.format("No units left of group %s. Group will be respawned now.",SpawnGroup:GetName())) self:_Respawn(SpawnGroup) end else self:E(self.lid..string.format("ERROR: Could not find ratcraft object for crashed group %s!",SpawnGroup:GetName())) end end else if self.Debug then self:E(self.lid.."ERROR: Group does not exist in RAT:_OnCrash()!") end end 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(self.lid.."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(self.lid..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(self.lid.."Unknown Airport category in _Waypoint()!") end end return RoutePoint end function RAT:_Routeinfo(waypoints,comment,waypointdescriptions) 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,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,waypointdescriptions[i],waypointdescriptions[i+1]) end text=text..string.format("Total distance = %6.1f km\n",total/1000) text=text..string.format("******************************************************\n") self:T2(self.lid..text) return total end function RAT:_AnticipatedGroupName(index) local index=index or self.SpawnIndex+1 return string.format("%s#%03d",self.alias,index) end function RAT:_ActivateUncontrolled() local idx={} local rat={} local nactive=0 for spawnindex,_ratcraft in pairs(self.ratcraft)do local ratcraft=_ratcraft 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(self.lid..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(self.lid..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(self.lid..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(self.lid..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(self.lid..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(self.lid..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(self.lid..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(self.lid..string.format("RAT group %s unit number %d: Parking = %s",self.alias,UnitID,tostring(UnitTemplate.parking))) self:T2(self.lid..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="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay = "..RAT.ATC.delay BASE:I(RAT.id..text) for _,ap in pairs(airports_map)do local airbase=ap local name=airbase:GetName() local fc=_DATABASE:GetFlightControl(name) if not fc then local airport={} airport.queue={} airport.busy=false airport.onfinal={} airport.Nonfinal=0 airport.traffic=0 airport.Tlastclearance=nil RAT.ATC.airport[name]=airport end end SCHEDULER:New(nil,RAT._ATCCheck,{},5,15) SCHEDULER:New(nil,RAT._ATCStatus,{},5,60) RAT.ATC.T0=timer.getTime() end RAT.ATC.init=true end function RAT:_ATCAddFlight(name,dest) BASE:I(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.",dest,name,dest)) local flight={} flight.destination=dest flight.Tarrive=-1 flight.holding=-1 flight.Tarrive=-1 RAT.ATC.flight[name]=flight end function RAT._ATCDelFlight(t,entry) for k,_ in pairs(t)do if k==entry then BASE:I(RAT.id..string.format("Removing flight %s from queue",entry)) t[entry]=nil end end end function RAT._ATCRegisterFlight(name,time) BASE:I(RAT.id..string.format("Flight %s registered at ATC for landing clearance.",name)) RAT.ATC.flight[name].Tarrive=time RAT.ATC.flight[name].holding=0 end function RAT._ATCStatus() local Tnow=timer.getTime() for name,_flight in pairs(RAT.ATC.flight)do local flight=_flight local hold=RAT.ATC.flight[name].holding local dest=RAT.ATC.flight[name].destination local airport=RAT.ATC.airport[dest] if airport then if hold>=0 then local busy="Runway state is unknown" if airport.Nonfinal>0 then busy="Runway is occupied by "..airport.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:I(RAT.id..text) elseif hold==RAT.ATC.onfinal then local Tfinal=Tnow-flight.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:I(RAT.id..text) elseif hold==RAT.ATC.unregistered then else BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") end else end end end function RAT._ATCCheck() RAT._ATCQueue() local Tnow=timer.getTime() for airportname,_airport in pairs(RAT.ATC.airport)do local airport=_airport for qID,flightname in pairs(airport.queue)do local flight=RAT.ATC.flight[flightname] local nqueue=#airport.queue local landing1=false if airport.Tlastclearance then landing1=(Tnow-airport.Tlastclearance>RAT.ATC.delay)and airport.Nonfinal=0 then flight.holding=Tnow-flight.Tarrive end local hold=flight.holding local dest=flight.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:I(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) delay=delay or 5 if delay and delay>0 then 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) self:ScheduleOnce(delay,RATMANAGER.Start,self,0) else 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 self:ScheduleOnce(time,RAT._SpawnWithRoute,self.rat[i]) end end for i=1,self.nrat do local rat=self.rat[i] if rat.uncontrolled and rat.activate_uncontrolled then local Tactivate=math.max(time+1,rat.activate_delay) self:ScheduleRepeat(Tactivate,rat.activate_delta,rat.activate_frand,nil,rat._ActivateUncontrolled,rat) 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:I(text) end return self end function RATMANAGER:Stop(delay) delay=delay or 1 if delay and delay>0 then self:I(RATMANAGER.id..string.format("Manager will be stopped in %d seconds.",delay)) self:ScheduleOnce(delay,RATMANAGER.Stop,self,0) else self:I(RATMANAGER.id..string.format("Stopping manager with scheduler ID %s",self.managerid)) self.manager:Stop(self.managerid) end 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() self:T(RATMANAGER.id..string.format("Number of alive groups %d. New groups to be spawned %d.",ntot,self.ntot-ntot)) 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 self:ScheduleOnce(time,RATMANAGER._Spawn,self,i) 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.9", 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, CaptureObjectCategories={Object.Category.UNIT}, ExcludeShips=true, } 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.ExcludeShips=true 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:SetStrategoZone(Zone) self.StrategoZone=Zone 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,CaptureCategories) self:T(self.lid.."SetCaptureOptions") self.CaptureUnits=CaptureUnits or 3 self.CaptureThreatlevel=CaptureThreatlevel or 1 self.CaptureObjectCategories=CaptureCategories or{Object.Category.UNIT} 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 local zone=self.StrategoZone self.bases:ForEach( function(afb) local ab=afb local abvec2=ab:GetVec2() if self.ExcludeShips and ab:IsShip()then return end if zone~=nil then if not zone:IsVec2InZone(abvec2)then return end end 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=STRATEGO.Type.AIRBASE if ab:IsShip()then numrwys=1 abtype=STRATEGO.Type.SHIP end if ab:IsHelipad()then numrwys=1 abtype=STRATEGO.Type.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, connections=0, } 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==STRATEGO.Type.AIRBASE or data.type==STRATEGO.Type.FARP or data.type==STRATEGO.Type.SHIP then data.coalition=AIRBASE:FindByName(data.name):GetCoalition()or 0 else data.coalition=data.opszone:GetOwner()or 0 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:SetObjectCategories(self.CaptureObjectCategories) 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, connections=0, } airbasetable[zname]=tbl nonconnectedab[zname]=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:GetRoutesFromNode(StartPoint) self:T(self.lid.."GetRoutesFromNode") local pstart=string.gsub(StartPoint,"[%p%s]",".") local found=false pstart=pstart..";" local routes={} local listed={} for _,_data in pairs(self.routexists)do if string.find(_data,pstart,1,true)and not listed[_data]then local target=string.gsub(_data,pstart,"") local fname=self.easynames[target] table.insert(routes,fname) found=true listed[_data]=true end end return found,routes 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 self.airbasetable[Endpoint].connections=self.airbasetable[Endpoint].connections+2 self.airbasetable[Startpoint].connections=self.airbasetable[Startpoint].connections+2 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 self.airbasetable[startpoint].connections=self.airbasetable[startpoint].connections+1 self.airbasetable[_data.name].connections=self.airbasetable[_data.name].connections+1 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 self:T("Found Consolidation Target: "..cname) shortest=dist target=cname weight=self.airbasetable[cname].weight coa=coa 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 if(dist=Weight)then self:T("Found Strategic Target: "..cname) 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 points>0 then self:T({dist=dist,name=name,points=points,coa=coa}) end if points~=0 then local enemycoa=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE self:T("Enemycoa = "..enemycoa) if coa==coalition.side.NEUTRAL then local tdata={} tdata.name=name tdata.dist=dist tdata.points=points+self.NeutralBenefit tdata.coalition=coa tdata.coalitionname=UTILS.GetCoalitionName(coa) tdata.coordinate=self.airbasetable[name].coord table.insert(targets,tdata) else local tdata={} tdata.name=name tdata.dist=dist tdata.points=points tdata.coalition=coa tdata.coalitionname=UTILS.GetCoalitionName(coa) tdata.coordinate=self.airbasetable[name].coord table.insert(targets,tdata) end 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 points>0 then self:T({dist=dist,name=name,points=points,coa=coa}) end if points~=0 then local enemycoa=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE self:T("Enemycoa = "..enemycoa) if coa==coalition.side.NEUTRAL then local tdata={} tdata.name=name tdata.dist=dist tdata.points=points+self.NeutralBenefit tdata.coalition=coa tdata.coalitionname=UTILS.GetCoalitionName(coa) tdata.coordinate=self.airbasetable[name].coord table.insert(targets,tdata) else local tdata={} tdata.name=name tdata.dist=dist tdata.points=points tdata.coalition=coa tdata.coalitionname=UTILS.GetCoalitionName(coa) tdata.coordinate=self.airbasetable[name].coord table.insert(targets,tdata) end 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] if cname then 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.dist2 and true or false if _name==End then nnodes=true end local dist=math.floor((kcoord:Get2DDistance(ecoord)/1000)+0.5) if(distlargestcut then largestcut=j-i cut={i=i,j=j} foundcut=true end end end end end if foundcut then local newroute={} for i=1,#Route do if i<=cut.i or i>=cut.j then table.insert(newroute,Route[i]) end end return newroute end return Route,foundcut end if routecomplete==true then local foundcut=true while foundcut~=false do Route,foundcut=OptimizeRoute(Route) end else Route,routecomplete=self:FindRoute(End,Start,Hops,Draw,Color,LineType) reverse=true end if(self.debug or Draw)then DrawRoute(Route)end return Route,routecomplete,reverse end function STRATEGO:AddBudget(Number) self:T(self.lid.."AddBudget") self.Budget=self.Budget+Number return self end function STRATEGO:SubtractBudget(Number) self:T(self.lid.."SubtractBudget") self.Budget=self.Budget-Number return self end function STRATEGO:GetBudget() self:T(self.lid.."GetBudget") return self.Budget end function STRATEGO:FindAffordableStrategicTarget() self:T(self.lid.."FindAffordableStrategicTarget") local Stargets=self:FindStrategicTargets() local budget=self.Budget local ftarget=nil local Targets={} for _,_data in pairs(Stargets)do local data=_data self:T("Considering Strategic Target "..data.name) if data.points<=budget then table.insert(Targets,data) self:T(self.lid.."Affordable strategic target: "..data.name) end end if#Targets==0 then self:T(self.lid.."No suitable target found!") return nil end if#Targets>1 then ftarget=Targets[math.random(1,#Targets)] else ftarget=Targets[1] end if ftarget then self:T(self.lid.."Final affordable strategic target: "..ftarget.name) return ftarget else return nil end end function STRATEGO:FindAffordableConsolidationTarget() self:T(self.lid.."FindAffordableConsolidationTarget") local Ctargets=self:FindConsolidationTargets() local budget=self.Budget local ftarget=nil local Targets={} for _,_data in pairs(Ctargets)do local data=_data self:T("Considering Consolidation Target "..data.name) if data.points<=budget then table.insert(Targets,data) self:T(self.lid.."Affordable consolidation target: "..data.name) end end if#Targets==0 then self:T(self.lid.."No suitable target found!") return nil end if#Targets>1 then ftarget=Targets[math.random(1,#Targets)] else ftarget=Targets[1] end if ftarget then self:T(self.lid.."Final affordable consolidation target: "..ftarget.name) return ftarget else return nil end end AIRBOSS={ ClassName="AIRBOSS", Debug=false, lid=nil, theatre=nil, carrier=nil, carriertype=nil, carrierparam={}, alias=nil, airbase=nil, waypoints={}, currentwp=nil, beacon=nil, TACANon=nil, TACANchannel=nil, TACANmode=nil, TACANmorse=nil, ICLSon=nil, ICLSchannel=nil, ICLSmorse=nil, LSORadio=nil, LSOFreq=nil, LSOModu=nil, MarshalRadio=nil, MarshalFreq=nil, MarshalModu=nil, TowerFreq=nil, radiotimer=nil, zoneCCA=nil, zoneCCZ=nil, players={}, menuadded={}, BreakEntry={}, BreakEarly={}, BreakLate={}, Abeam={}, Ninety={}, Wake={}, Final={}, Groove={}, Platform={}, DirtyUp={}, Bullseye={}, defaultcase=nil, case=nil, defaultoffset=nil, holdingoffset=nil, recoverytimes={}, flights={}, Qpattern={}, Qmarshal={}, Qwaiting={}, Qspinning={}, RQMarshal={}, RQLSO={}, TQMarshal=0, TQLSO=0, Nmaxpattern=nil, Nmaxmarshal=nil, NmaxSection=nil, NmaxStack=nil, handleai=nil, xtVoiceOvers=nil, xtVoiceOversAI=nil, tanker=nil, Corientation=nil, Corientlast=nil, Cposition=nil, defaultskill=nil, adinfinitum=nil, magvar=nil, Tcollapse=nil, recoverywindow=nil, usersoundradio=nil, Tqueue=nil, dTqueue=nil, dTstatus=nil, menumarkzones=nil, menusmokezones=nil, playerscores=nil, autosave=nil, autosavefile=nil, autosavepath=nil, marshalradius=nil, airbossnice=nil, staticweather=nil, windowcount=0, LSOdT=nil, senderac=nil, radiorelayLSO=nil, radiorelayMSH=nil, turnintowind=nil, detour=nil, squadsetAI=nil, excludesetAI=nil, menusingle=nil, collisiondist=nil, holdtimestamp=nil, Tmessage=nil, soundfolder=nil, soundfolderLSO=nil, soundfolderMSH=nil, despawnshutdown=nil, dTbeacon=nil, Tbeacon=nil, LSOCall=nil, MarshalCall=nil, lowfuelAI=nil, emergency=nil, respawnAI=nil, gle={}, lue={}, trapsheet=nil, trappath=nil, trapprefix=nil, initialmaxalt=nil, welcome=nil, skipperMenu=nil, skipperSpeed=nil, skipperTime=nil, skipperOffset=nil, skipperUturn=nil, } AIRBOSS.AircraftCarrier={ AV8B="AV8BNA", HORNET="FA-18C_hornet", A4EC="A-4E-C", F14A="F-14A-135-GR", F14B="F-14B", F14A_AI="F-14A", FA18C="F/A-18C", T45C="T-45", S3B="S-3B", S3BTANKER="S-3B Tanker", E2D="E-2C", C2A="C2A_Greyhound", RHINOE="FA-18E", RHINOF="FA-18F", GROWLER="EA-18G", } AIRBOSS.CarrierType={ ROOSEVELT="CVN_71", LINCOLN="CVN_72", WASHINGTON="CVN_73", TRUMAN="CVN_75", STENNIS="Stennis", FORRESTAL="Forrestal", VINSON="VINSON", HERMES="HERMES81", INVINCIBLE="hms_invincible", TARAWA="LHA_Tarawa", AMERICA="USS America LHA-6", JCARLOS="L61", CANBERRA="L02", KUZNETSOV="KUZNECOW", } AIRBOSS.PatternStep={ UNDEFINED="Undefined", REFUELING="Refueling", SPINNING="Spinning", COMMENCING="Commencing", HOLDING="Holding", WAITING="Waiting for free Marshal stack", PLATFORM="Platform", ARCIN="Arc Turn In", ARCOUT="Arc Turn Out", DIRTYUP="Dirty Up", BULLSEYE="Bullseye", INITIAL="Initial", BREAKENTRY="Break Entry", EARLYBREAK="Early Break", LATEBREAK="Late Break", ABEAM="Abeam", NINETY="Ninety", WAKE="Wake", FINAL="Turn Final", GROOVE_XX="Groove X", GROOVE_IM="Groove In the Middle", GROOVE_IC="Groove In Close", GROOVE_AR="Groove At the Ramp", GROOVE_IW="Groove In the Wires", GROOVE_AL="Groove Abeam Landing Spot", GROOVE_LC="Groove Level Cross", BOLTER="Bolter Pattern", EMERGENCY="Emergency Landing", DEBRIEF="Debrief", } AIRBOSS.GroovePos={ X0="X0", XX="XX", IM="IM", IC="IC", AR="AR", AL="AL", LC="LC", IW="IW", } AIRBOSS.Difficulty={ EASY="Flight Student", NORMAL="Naval Aviator", HARD="TOPGUN Graduate", } AIRBOSS.MenuF10={} AIRBOSS.MenuF10Root=nil AIRBOSS.version="1.3.3" function AIRBOSS:New(carriername,alias) local self=BASE:Inherit(self,FSM:New()) self:F2({carriername=carriername,alias=alias}) self.carrier=UNIT:FindByName(carriername) if self.carrier==nil then local text=string.format("ERROR: Carrier unit %s could not be found! Make sure this UNIT is defined in the mission editor and check the spelling of the unit name carefully.",carriername) MESSAGE:New(text,120):ToAll() self:E(text) return nil end self.lid=string.format("AIRBOSS %s | ",carriername) self.theatre=env.mission.theatre self:T2(self.lid..string.format("Theatre = %s.",tostring(self.theatre))) self.carriertype=self.carrier:GetTypeName() self.alias=alias or carriername self.airbase=AIRBASE:FindByName(carriername) self.beacon=BEACON:New(self.carrier) self:_GetTowerFrequency() self.playerscores={} self:_InitWaypoints() self.currentwp=1 self:_PatrolRoute() self:SetMarshalRadio() self:SetAirbossRadio() self:SetLSORadio() self:SetLSOCallInterval() self.radiotimer=SCHEDULER:New() self:SetMagneticDeclination() self:SetICLS() self:SetTACAN() self:SetBeaconRefresh() self:SetMaxLandingPattern() self:SetMaxMarshalStacks() self:SetMaxSectionSize() self:SetMaxFlightsPerStack() self:SetHandleAION() self:SetExtraVoiceOvers(false) self:SetExtraVoiceOversAI(false) self:SetAirbossNiceGuy() self:SetEmergencyLandings() self:SetDespawnOnEngineShutdown(false) self:SetRespawnAI(false) self:SetStaticWeather() self:SetRecoveryCase() self:SetRecoveryTurnTime() self:SetHoldingOffsetAngle() self:SetMarshalRadius() self:SetInitialMaxAlt() self:SetDefaultPlayerSkill(AIRBOSS.Difficulty.EASY) self:SetGlideslopeErrorThresholds() self:SetLineupErrorThresholds() self:SetCarrierControlledArea() self:SetCarrierControlledZone() self:SetPatrolAdInfinitum(true) self:SetCollisionDistance() self:SetQueueUpdateTime() self:SetStatusUpdateTime() self:SetDefaultMessageDuration() self:SetMenuMarkZones() self:SetMenuSmokeZones() self:SetMenuSingleCarrier(false) self:SetWelcomePlayers(true) self.landingcoord=COORDINATE:New(0,0,0) self.sterncoord=COORDINATE:New(0,0,0) self.landingspotcoord=COORDINATE:New(0,0,0) if self.carriertype==AIRBOSS.CarrierType.STENNIS then self:_InitNimitz() elseif self.carriertype==AIRBOSS.CarrierType.ROOSEVELT then self:_InitNimitz() elseif self.carriertype==AIRBOSS.CarrierType.LINCOLN then self:_InitNimitz() elseif self.carriertype==AIRBOSS.CarrierType.WASHINGTON then self:_InitNimitz() elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then self:_InitNimitz() elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then self:_InitForrestal() elseif self.carriertype==AIRBOSS.CarrierType.VINSON then self:_InitStennis() elseif self.carriertype==AIRBOSS.CarrierType.HERMES then self:_InitHermes() elseif self.carriertype==AIRBOSS.CarrierType.INVINCIBLE then self:_InitInvincible() elseif self.carriertype==AIRBOSS.CarrierType.TARAWA then self:_InitTarawa() elseif self.carriertype==AIRBOSS.CarrierType.AMERICA then self:_InitAmerica() elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then self:_InitJcarlos() elseif self.carriertype==AIRBOSS.CarrierType.CANBERRA then self:_InitCanberra() elseif self.carriertype==AIRBOSS.CarrierType.KUZNETSOV then self:_InitStennis() else self:E(self.lid..string.format("ERROR: Unknown carrier type %s!",tostring(self.carriertype))) return nil end self:_InitVoiceOvers() if false then self.Debug=true BASE:TraceOnOff(true) BASE:TraceClass(self.ClassName) BASE:TraceLevel(3) end if false then local case=3 self.holdingoffset=30 self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red,5) self:_GetZoneLineup():SmokeZone(SMOKECOLOR.Green,5) self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.White,45) self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange,45) self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue,45) self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue,45) self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Blue,45) self:_GetZoneCorridor(case):SmokeZone(SMOKECOLOR.Green,45) self:_GetZoneHolding(case,1):SmokeZone(SMOKECOLOR.White,45) self:_GetZoneHolding(case,2):SmokeZone(SMOKECOLOR.White,45) self:_GetZoneInitial(case):SmokeZone(SMOKECOLOR.Orange,45) self:_GetZoneCommence(case,1):SmokeZone(SMOKECOLOR.Red,45) self:_GetZoneCommence(case,2):SmokeZone(SMOKECOLOR.Red,45) self:_GetZoneAbeamLandingSpot():SmokeZone(SMOKECOLOR.Red,5) self:_GetZoneLandingSpot():SmokeZone(SMOKECOLOR.Red,5) end if false then local FB=self:GetFinalBearing(false) local hdg=self:GetHeading(false) local stern=self:_GetSternCoord() local bow=stern:Translate(self.carrierparam.totlength,hdg,true) local rwy=stern:Translate(self.carrierparam.rwylength,FB,true) local function flareme() self:GetCoordinate():FlareYellow() stern:FlareYellow() bow:FlareYellow() local r1=stern:Translate(self.carrierparam.rwywidth*0.5,FB+90,true) local r2=stern:Translate(self.carrierparam.rwywidth*0.5,FB-90,true) rwy:FlareRed() local cR=stern:Translate(self.carrierparam.totwidthstarboard,hdg+90,true) local cL=stern:Translate(self.carrierparam.totwidthport,hdg-90,true) if self.carrier:GetTypeName()~=AIRBOSS.CarrierType.INVINCIBLE or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.CANBERRA then local w1=stern:Translate(self.carrierparam.wire1,FB,true) local w2=stern:Translate(self.carrierparam.wire2,FB,true) local w3=stern:Translate(self.carrierparam.wire3,FB,true) local w4=stern:Translate(self.carrierparam.wire4,FB,true) w1:FlareWhite() w2:FlareYellow() w3:FlareWhite() w4:FlareYellow() else local ALSPT=self:_GetZoneAbeamLandingSpot() ALSPT:FlareZone(FLARECOLOR.Red,5,nil,UTILS.FeetToMeters(120)) local LSPT=self:_GetZoneLandingSpot() LSPT:FlareZone(FLARECOLOR.Green,5,nil,self.carrierparam.deckheight) local PLSC=self:_GetLandingSpotCoordinate() PLSC:FlareWhite() end local cbox=self:_GetZoneCarrierBox() local rbox=self:_GetZoneRunwayBox() cbox:FlareZone(FLARECOLOR.Green,5,nil,self.carrierparam.deckheight) rbox:FlareZone(FLARECOLOR.White,5,nil,self.carrierparam.deckheight) end SCHEDULER:New(nil,flareme,{},1,3,nil,180) end self:SetStartState("Stopped") self:AddTransition("Stopped","Load","Stopped") self:AddTransition("Stopped","Start","Idle") self:AddTransition("*","Idle","Idle") self:AddTransition("Idle","RecoveryStart","Recovering") self:AddTransition("Recovering","RecoveryStop","Idle") self:AddTransition("Recovering","RecoveryPause","Paused") self:AddTransition("Paused","RecoveryUnpause","Recovering") self:AddTransition("*","Status","*") self:AddTransition("*","RecoveryCase","*") self:AddTransition("*","PassingWaypoint","*") self:AddTransition("*","LSOGrade","*") self:AddTransition("*","Marshal","*") self:AddTransition("*","Save","*") self:AddTransition("*","Stop","Stopped") return self end function AIRBOSS:SetWelcomePlayers(Switch) self.welcome=Switch return self end function AIRBOSS:SetCarrierControlledArea(Radius) Radius=UTILS.NMToMeters(Radius or 50) self.zoneCCA=ZONE_UNIT:New("Carrier Controlled Area",self.carrier,Radius) return self end function AIRBOSS:SetCarrierControlledZone(Radius) Radius=UTILS.NMToMeters(Radius or 5) self.zoneCCZ=ZONE_UNIT:New("Carrier Controlled Zone",self.carrier,Radius) return self end function AIRBOSS:SetCollisionDistance(Distance) self.collisiondist=UTILS.NMToMeters(Distance or 5) return self end function AIRBOSS:SetRecoveryCase(Case) self.defaultcase=Case or 1 self.case=self.defaultcase return self end function AIRBOSS:SetHoldingOffsetAngle(Offset) self.defaultoffset=Offset or 0 self.holdingoffset=self.defaultoffset return self end function AIRBOSS:SetMenuRecovery(Duration,WindOnDeck,Uturn,Offset) self.skipperMenu=true self.skipperTime=Duration or 30 self.skipperSpeed=WindOnDeck or 25 self.skipperOffset=Offset or 30 if Uturn then self.skipperUturn=true else self.skipperUturn=false end return self end function AIRBOSS:AddRecoveryWindow(starttime,stoptime,case,holdingoffset,turnintowind,speed,uturn) local Tnow=timer.getAbsTime() if starttime and type(starttime)=="number"then starttime=UTILS.SecondsToClock(Tnow+starttime) end if stoptime and type(stoptime)=="number"then stoptime=UTILS.SecondsToClock(Tnow+stoptime) end starttime=starttime or UTILS.SecondsToClock(Tnow) local Tstart=UTILS.ClockToSeconds(starttime) local Tstop=stoptime and UTILS.ClockToSeconds(stoptime)or Tstart+90*60 if Tstart>Tstop 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:SetIntoWindLegacy(SwitchOn) if SwitchOn==nil then SwitchOn=true end self.intowindold=SwitchOn 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() if require then self.magvar=pos:GetMagneticDeclination() end 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}, GREYHOUND={file="PILOT-Greyhound",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 or flight.actype==AIRBOSS.AircraftCarrier.C2A 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(vdeck,magnetic,coord) if self.intowindold then return self:GetHeadingIntoWind_old(vdeck,magnetic,coord) else return self:GetHeadingIntoWind_new(vdeck,magnetic,coord) end end function AIRBOSS:GetHeadingIntoWind_old(vdeck,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 local vtot=math.max(vdeck-UTILS.MpsToKnots(vwind),4) return intowind,vtot end function AIRBOSS:GetHeadingIntoWind_new(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.C2A then nickname="Greyhound" 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}, ["MH-60R"]={type="MH-60R",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, ["SH-60B"]={type="SH-60B",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}, ["OH-6A"]={type="OH-6A",crates=false,troops=true,cratelimit=0,trooplimit=4,length=7,cargoweightlimit=550}, } CTLD.version="1.0.53" 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) local TNow=timer.getTime() local vtime=v.ExtractTime or TNow-310 if distance<=extractdistance and distance~=-1 and(TNow-vtime>300)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) nearestGroup.ExtractTime=0 else self.CargoCounter=self.CargoCounter+1 nearestGroup.ExtractTime=timer.getTime() 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}) local running=math.floor(nearestDistance/4)+10 loaded.Troopsloaded=loaded.Troopsloaded+troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname]=loaded self:ScheduleOnce(running,self._SendMessage,self,"Troops boarded!",10,false,Group) self:_SendMessage("Troops boarding!",10,false,Group) self:_UpdateUnitCargoMass(Unit) self:__TroopsExtracted(running,Group,Unit,nearestGroup) local coord=Unit:GetCoordinate()or Group:GetCoordinate() local Point if coord then local heading=unit:GetHeading()or 0 local Angle=math.floor((heading+160)%360) Point=coord:Translate(8,Angle):GetVec2() if Point then nearestGroup:RouteToVec2(Point,4) end end 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,running) 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,15) 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 location=Cargo:GetLocation() if location then local unitcoord=Unit:GetCoordinate()or Group:GetCoordinate() if unitcoord then if not location:IsCoordinateInZone(unitcoord)then self:_SendMessage("The requested cargo is not available in this zone!",10,false,Group) if not self.debug then return self end end 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) local numbertroops=#template.units local slightshift=math.abs(math.random(0,200)/100) local newcenter=Coordinate:Translate(Radius+slightshift,((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 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 outcome==true and zone~=nil 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 local noshow=entry.DontShowInMenu if not noshow then menucount=menucount+1 menus[menucount]=MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops,self,_group,_unit,entry) end end else for _,_entry in pairs(self.Cargo_Troops)do local entry=_entry local noshow=entry.DontShowInMenu if not noshow then menucount=menucount+1 menus[menucount]=MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops,self,_group,_unit,entry) end 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 local noshow=entry.DontShowInMenu local zone=entry.Location if not noshow then menucount=menucount+1 local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if zone then menutext=string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates,self,_group,_unit,entry) end end for _,_entry in pairs(self.Cargo_Statics)do local entry=_entry local subcat=entry.Subcategory local noshow=entry.DontShowInMenu local zone=entry.Location if not noshow then menucount=menucount+1 local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if zone then menutext=string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates,self,_group,_unit,entry) end end else for _,_entry in pairs(self.Cargo_Crates)do local entry=_entry local noshow=entry.DontShowInMenu local zone=entry.Location if not noshow then menucount=menucount+1 local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if zone then menutext=string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates,self,_group,_unit,entry) end end for _,_entry in pairs(self.Cargo_Statics)do local entry=_entry local noshow=entry.DontShowInMenu local zone=entry.Location if not noshow then menucount=menucount+1 local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if zone then menutext=string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates,self,_group,_unit,entry) end end end listmenu=MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit) local 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,DontShowInMenu,Location) 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,DontShowInMenu,Location) table.insert(self.Cargo_Crates,cargo) return self end function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location) 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,DontShowInMenu,Location) 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,DontShowInMenu,Location) 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,DontShowInMenu,Location) 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) self:T("Distance Zone: "..distance) if(zone:IsVec2InZone(unitVec2)or Zonetype==CTLD.CargoZoneType.MOVE)and active==true and maxdist>distance then outcome=true 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.AircraftType["MH-60R"]=10 CSAR.AircraftType["OH-6A"]=2 CSAR.version="1.0.23" 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=MSRS.Voices.Google.Standard.en_GB_Standard_B 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) :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,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) if self.coordtype~=2 then self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) else self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true) local coordtext=UTILS.MGRSStringToSRSFriendly(_coordinatesText,true) local _text=string.format("%s requests SAR at %s, beacon at %.2f kilo hertz",_groupName,coordtext,_freqk) self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false) end else local _text=string.format("Pickup Zone at %s.",_coordinatesText) if self.coordtype~=2 then self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) else self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true) local coordtext=UTILS.MGRSStringToSRSFriendly(_coordinatesText,true) local _text=string.format("Pickup Zone at %s.",coordtext) self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false) end 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 self:T(self.lid.."[Pickup Debug] Helo closer than 500m: ".._lookupKeyHeli) 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 self:T(self.lid.."[Pickup Debug] Checking landed vs Hover for ".._lookupKeyHeli) if not _heliUnit:InAir()then self:T(self.lid.."[Pickup Debug] Helo landed: ".._lookupKeyHeli) if self.pilotRuntoExtractPoint==true then if(_distance0 then self:T(self.lid.."[Pickup Debug] Helo hovering not long enough ".._lookupKeyHeli) 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 self:T(self.lid.."[Pickup Debug] Helo hovering long enough - door check ".._lookupKeyHeli) if self.pilotmustopendoors and(self:_IsLoadingDoorOpen(_heliName)==false)then self:_DisplayMessageToSAR(_heliUnit,"Open the door to let me in!",self.messageTime,true,true) self:T(self.lid.."[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) return false else self.hoverStatus[_lookupKeyHeli]=nil self:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName) self:T(self.lid.."[Pickup Debug] Pilot picked up ".._lookupKeyHeli) return true end end _reset=false else self:T(self.lid.."[Pickup Debug] Helo hovering too high ".._lookupKeyHeli) self:_DisplayMessageToSAR(_heliUnit,"Too high to winch ".._pilotName.." \nReduce height and hover for 10 seconds!",self.messageTime,true,true) self:T(self.lid.."[Pickup Debug] Hovering too high, try again next loop ".._lookupKeyHeli) 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,noreschedule) 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 self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance can not be determined!") return end self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) 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.name}) 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}) 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)) local wp=self:GetWaypointNext() self.option.Formation=wp.action self:__Cruise(-1) 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 self:T(self.lid..string.format("Expected/waypoint speed=%.1f m/s",self.speedWp)) 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..string.format("Cruise ==> Update route in 0.01 sec (speed=%s, formation=%s)",tostring(Speed),tostring(Formation))) 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,Delay) if Delay and Delay>0 then self:ScheduleOnce(Delay,ARMYGROUP._InitGroup,self,Template,0) else 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 and self.speedMax>3.6 then self.isMobile=true else self.isMobile=false self.speedMax=0 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 if self.groupinitialized then self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") return end self:T(self.lid.."FF Initializing Group") 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() local u=dcsgroup:getUnits() if#units~=size0 then self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units! u=%d",#units,size0,#u)) end for _,unit in pairs(units)do local unitname=unit:GetName() self:_AddElementByName(unitname) end self.groupinitialized=true end 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.64", 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") if not self.AwacsSRS then MESSAGE:New("AWACS: Setup SRS in your code BEFORE trying to add tac radios please!",30,"ERROR",true):ToLog():ToAll() return self end 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.Backend) 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,Backend) 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.Backend=Backend or MSRS.backend BASE:I({backend=self.Backend}) self.AwacsSRS=MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation,self.Backend) 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 then self:T(self.lid.."Merged") self:_MergedCall(_id) end if(pilot.IsPlayer or self.debug)and distnm>5 and distnm<=self.ThreatDistance then self:_ThreatRangeCall(_id,Contact) end if(pilot.IsPlayer or self.debug)and distnm>self.ThreatDistance and distnm<=self.MeldDistance then self:_MeldRangeCall(_id,Contact) end if(pilot.IsPlayer or self.debug)and distnm>self.MeldDistance and distnm<=self.TacDistance then self:_TACRangeCall(_id,Contact) 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,text,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") local STN=self.STN report:Add("====================") report:Add(string.format("AWACS %s",self.callsigntxt)) report:Add(string.format("Radio: %.3f %s",self.Frequency,UTILS.GetModulationName(self.Modulation))) if STN then report:Add(string.format("Link-16 STN: %s",STN)) end 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 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) 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 local name=managedgroup.GroupName if contact 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) if not self.TacticalSubscribers[name]then self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) end self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,false,AWACS.TaskStatus.EXECUTING) if GID and GID~=0 then if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then if self.TacticalSubscribers[name]then self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true) end end end 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 or"Bogey" local name=managedgroup.GroupName if contact 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) if not self.TacticalSubscribers[name]then self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) end self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,true,AWACS.TaskStatus.EXECUTING) if GID and GID~=0 then 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 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 or"Bogey" local name=managedgroup.GroupName local IsSub=self.TacticalSubscribers[name]and true or false 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) if IsSub==false then self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) end if GID and GID~=0 then 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 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) local managedgroup=self.ManagedGrps[GID] local name if managedgroup then name=managedgroup.GroupName or"none" end if not self.TacticalSubscribers[name]then self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) end if GID and GID~=0 then local managedgroup=self.ManagedGrps[GID] if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then 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() local unit=awacs:GetUnit(1) if unit then self.STN=tostring(unit:GetSTN()) end 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 not self:Is("Stopped")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 self.callsign={} self.callsign.NumberSquad=Callsign self.callsign.NumberGroup=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.4" 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=Speed or 350 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=Speed or 350 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=Speed or 350 awacszone.speed=Speed or 350 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=Speed or 350 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() if Trepair==0 then self:RunwayRepaired() else self:I(self.lid..string.format("Runway still destroyed! Will be repaired in %d sec",Trepair)) 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,tostring(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() self:T2(self.lid..string.format("FF parking spot %d is occupied by unit %s alive=%s",spot.TerminalID,unitname,tostring(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.holdtime=2*60 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:T(self.lid.."Set Ready for Takeoff switch for flightcontrol") 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",tostring(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:T(self.lid.."Calling RTB in onafterOutOfMissilesAA") 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:T(self.lid.."Calling RTB in onafterOutOfMissilesAG") 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 if self.currbase==nil or self.currbase.AirbaseName~=destbase.AirbaseName then self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") self:__RTB(-0.1,destbase) end 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:T(self.lid.."Calling RTB in onbeforeRTB") 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=self.holdtime 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:T(self.lid.."Calling RTB in onafterFuelLow") 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:T(self.lid.."Calling RTB in onafterFuelCritical") 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 and self.speedMax>3.6 then self.isMobile=true else self.isMobile=false self.speedMax=0 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:_CheckStuck(Despawn) if not self:IsTaxiing()then return nil end local Tnow=timer.getTime() local ExpectedSpeed=5 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 local holdtime=nil if self.stuckTimestamp then holdtime=Tnow-self.stuckTimestamp self:Stuck(holdtime) if holdtime>=5*60 and holdtime<15*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)) elseif holdtime>=15*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) end if self.stuckDespawn then if self.legion then self:T(self.lid..string.format("Asset is returned to its legion after being stuck!")) self:ReturnToLegion() else self:T(self.lid..string.format("Despawning group after being stuck!")) self:Despawn() end end end end return holdtime 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+30 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 and self.speedMax>3.6 then self.isMobile=true else self.isMobile=false self.speedMax=0 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.1" 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:E(self.lid.."ERROR: GROUP does not exist! Returning nil") return nil end end if UTILS.IsInstanceOf(group,"OPSGROUP")then self:E(self.lid.."ERROR: GROUP is already an OPSGROUP: "..tostring(self.groupname).."!") return group 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] if masterunit then 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 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("*","Stuck","*") 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 else self:E(self.lid..string.format("ERROR: DCS group does not exist! Cannot get 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 self:T(self.lid.."Destroying group!") 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:T2(self.lid..string.format("DCS unit=%s isExist=%s",tostring(EventData.IniDCSUnit:getName()),tostring(EventData.IniDCSUnit:isExist()))) 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 self:T(self.lid..string.format("Expected/waypoint speed=%.1f m/s",self.speedWp)) 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 self:T(self.lid..string.format("Expected/waypoint speed=%.1f m/s",self.speedWp)) 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.Emission=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:HasClients() self:T(self.lid.."HasClients?") local hasclients=self:CountClients()>0 and true or false return hasclients 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" AUFTRAG.Type.CONQUER="Conquer" 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, [AUFTRAG.Type.CONQUER]=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", ELEVATION="\nTarget Elevation: %s %s", METER="meter", FEET="feet", 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", ELEVATION="\nZiel Höhe: %s %s", METER="Meter", FEET="Fuss", 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.66" 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()or COORDINATE:New(0,0,0) local Elevation=Coordinate:GetLandHeight()or 0 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) local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS local elevationmeasure=self.gettext:GetEntry("FEET",self.locale) if settings:IsMetric()then elevationmeasure=self.gettext:GetEntry("METER",self.locale) else Elevation=math.floor(UTILS.MetersToFeet(Elevation)) end local elev=self.gettext:GetEntry("ELEVATION",self.locale) text=text..string.format(elev,tostring(math.floor(Elevation)),elevationmeasure) 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:T(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:T(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() if not FromCoord then return end 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:T("RTB and near the airbase!") self:Home() return end if not AIGroup:InAir()==true then self:T("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:T("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:T("Group "..self.Controllable:GetName().." ... Holding! ( "..self:GetState().." )") if AIGroup and AIGroup:IsAlive()then local Coordinate=AIGroup:GetCoordinate() if Coordinate==nil then return end local OrbitTask=AIGroup:TaskOrbitCircle(math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude),self.PatrolMinSpeed,Coordinate) 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:T({"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:T("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:T(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:T({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:GetRandomSurely():GetPointVec3() if TargetCoord==nil then self:Return() return end 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:T(DefenderGroupName..": No targets found -> Going RTB") self:Return() end end function AI_AIR_ENGAGE.___Engage(AIGroup,Fsm,AttackSetUnit) Fsm:T(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:GetRandomSurely():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:T(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:T(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:T(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:T("Captured "..AirbaseName) for SquadronName,Squadron in pairs(self.DefenderSquadrons)do if Squadron.AirbaseName==AirbaseName then Squadron.ResourceCount=-999 Squadron.Captured=true self:T("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:T({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:T({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:T({Airbases=AirbaseNames}) self:T("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:T({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:T({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:T("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:T({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:T({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:T({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:T({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:T({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:T({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:T({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:T({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:GetRandomSurely() 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:T({EscortAirbase=self.EscortAirbase}) self:T({PlayerGroupName=PlayerGroupName}) self:T({PlayerGroup=PlayerGroup}) self:T({FirstGroup=self.CarrierSet:GetFirst()}) self:T({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:T({EscortAirbase=self.EscortAirbase}) self:T({PlayerGroupName=PlayerGroupName}) self:T({PlayerGroup=PlayerGroup}) self:T({FirstGroup=self.CarrierSet:GetFirst()}) self:T({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:T({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:T({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:T(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 SHAPE_BASE={ ClassName="SHAPE_BASE", Name="", CenterVec2=nil, Points={}, Coords={}, MarkIDs={}, ColorString="", ColorRGBA={} } function SHAPE_BASE:New() local self=BASE:Inherit(self,BASE:New()) return self end function SHAPE_BASE:FindOnMap(shape_name) local self=BASE:Inherit(self,BASE:New()) local found=false for _,layer in pairs(env.mission.drawings.layers)do for _,object in pairs(layer["objects"])do if object["name"]==shape_name then self.Name=object["name"] self.CenterVec2={x=object["mapX"],y=object["mapY"]} self.ColorString=object["colorString"] self.ColorRGBA=UTILS.HexToRGBA(self.ColorString) found=true end end end if not found then self:E("Can't find a shape with name "..shape_name) end return self end function SHAPE_BASE:GetAllShapes(filter) filter=filter or"" local return_shapes={} for _,layer in pairs(env.mission.drawings.layers)do for _,object in pairs(layer["objects"])do if string.contains(object["name"],filter)then table.add(return_shapes,object) end end end return return_shapes end function SHAPE_BASE:Offset(new_vec2) local offset_vec2=UTILS.Vec2Subtract(new_vec2,self.CenterVec2) self.CenterVec2=new_vec2 if self.ClassName=="POLYGON"then for _,point in pairs(self.Points)do point.x=point.x+offset_vec2.x point.y=point.y+offset_vec2.y end end end function SHAPE_BASE:GetName() return self.Name end function SHAPE_BASE:GetColorString() return self.ColorString end function SHAPE_BASE:GetColorRGBA() return self.ColorRGBA end function SHAPE_BASE:GetColorRed() return self.ColorRGBA.R end function SHAPE_BASE:GetColorGreen() return self.ColorRGBA.G end function SHAPE_BASE:GetColorBlue() return self.ColorRGBA.B end function SHAPE_BASE:GetColorAlpha() return self.ColorRGBA.A end function SHAPE_BASE:GetCenterVec2() return self.CenterVec2 end function SHAPE_BASE:GetCenterCoordinate() return COORDINATE:NewFromVec2(self.CenterVec2) end function SHAPE_BASE:GetCoordinate() return self:GetCenterCoordinate() end function SHAPE_BASE:ContainsPoint(_) self:E("This needs to be set in the derived class") end function SHAPE_BASE:ContainsUnit(unit_name) local unit=UNIT:FindByName(unit_name) if unit==nil or not unit:IsAlive()then return false end if self:ContainsPoint(unit:GetVec2())then return true end return false end function SHAPE_BASE:ContainsAnyOfGroup(group_name) local group=GROUP:FindByName(group_name) if group==nil or not group:IsAlive()then return false end for _,unit in pairs(group:GetUnits())do if self:ContainsPoint(unit:GetVec2())then return true end end return false end function SHAPE_BASE:ContainsAllOfGroup(group_name) local group=GROUP:FindByName(group_name) if group==nil or not group:IsAlive()then return false end for _,unit in pairs(group:GetUnits())do if not self:ContainsPoint(unit:GetVec2())then return false end end return true end CIRCLE={ ClassName="CIRCLE", Radius=nil, } function CIRCLE:FindOnMap(shape_name) local self=BASE:Inherit(self,SHAPE_BASE:FindOnMap(shape_name)) for _,layer in pairs(env.mission.drawings.layers)do for _,object in pairs(layer["objects"])do if string.find(object["name"],shape_name,1,true)then if object["polygonMode"]=="circle"then self.Radius=object["radius"] end end end end return self end function CIRCLE:Find(shape_name) return _DATABASE:FindShape(shape_name) end function CIRCLE:New(vec2,radius) local self=BASE:Inherit(self,SHAPE_BASE:New()) self.CenterVec2=vec2 self.Radius=radius return self end function CIRCLE:GetRadius() return self.Radius end function CIRCLE:ContainsPoint(point) if((point.x-self.CenterVec2.x)^2+(point.y-self.CenterVec2.y)^2)^0.5<=self.Radius then return true end return false end function CIRCLE:PointInSector(point,sector_start,sector_end,center,radius) center=center or self.CenterVec2 radius=radius or self.Radius local function are_clockwise(v1,v2) return-v1.x*v2.y+v1.y*v2.x>0 end local function is_in_radius(rp) return rp.x*rp.x+rp.y*rp.y<=radius^2 end local rel_pt={ x=point.x-center.x, y=point.y-center.y } local rel_sector_start={ x=sector_start.x-center.x, y=sector_start.y-center.y, } local rel_sector_end={ x=sector_end.x-center.x, y=sector_end.y-center.y, } return not are_clockwise(rel_sector_start,rel_pt)and are_clockwise(rel_sector_end,rel_pt)and is_in_radius(rel_pt,radius) end function CIRCLE:UnitInSector(unit_name,sector_start,sector_end,center,radius) center=center or self.CenterVec2 radius=radius or self.Radius if self:PointInSector(UNIT:FindByName(unit_name):GetVec2(),sector_start,sector_end,center,radius)then return true end return false end function CIRCLE:AnyOfGroupInSector(group_name,sector_start,sector_end,center,radius) center=center or self.CenterVec2 radius=radius or self.Radius for _,unit in pairs(GROUP:FindByName(group_name):GetUnits())do if self:PointInSector(unit:GetVec2(),sector_start,sector_end,center,radius)then return true end end return false end function CIRCLE:AllOfGroupInSector(group_name,sector_start,sector_end,center,radius) center=center or self.CenterVec2 radius=radius or self.Radius for _,unit in pairs(GROUP:FindByName(group_name):GetUnits())do if not self:PointInSector(unit:GetVec2(),sector_start,sector_end,center,radius)then return false end end return true end function CIRCLE:UnitInRadius(unit_name,center,radius) center=center or self.CenterVec2 radius=radius or self.Radius if UTILS.IsInRadius(center,UNIT:FindByName(unit_name):GetVec2(),radius)then return true end return false end function CIRCLE:AnyOfGroupInRadius(group_name,center,radius) center=center or self.CenterVec2 radius=radius or self.Radius for _,unit in pairs(GROUP:FindByName(group_name):GetUnits())do if UTILS.IsInRadius(center,unit:GetVec2(),radius)then return true end end return false end function CIRCLE:AllOfGroupInRadius(group_name,center,radius) center=center or self.CenterVec2 radius=radius or self.Radius for _,unit in pairs(GROUP:FindByName(group_name):GetUnits())do if not UTILS.IsInRadius(center,unit:GetVec2(),radius)then return false end end return true end function CIRCLE:GetRandomVec2() local angle=math.random()*2*math.pi local rx=math.random(0,self.Radius)*math.cos(angle)+self.CenterVec2.x local ry=math.random(0,self.Radius)*math.sin(angle)+self.CenterVec2.y return{x=rx,y=ry} end function CIRCLE:GetRandomVec2OnBorder() local angle=math.random()*2*math.pi local rx=self.Radius*math.cos(angle)+self.CenterVec2.x local ry=self.Radius*math.sin(angle)+self.CenterVec2.y return{x=rx,y=ry} end function CIRCLE:GetBoundingBox() local min_x=self.CenterVec2.x-self.Radius local min_y=self.CenterVec2.y-self.Radius local max_x=self.CenterVec2.x+self.Radius local max_y=self.CenterVec2.y+self.Radius 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 CUBE={ ClassName="CUBE", Points={}, Coords={} } function CUBE:New(p1,p2,p3,p4,p5,p6,p7,p8) local self=BASE:Inherit(self,SHAPE_BASE) self.Points={p1,p2,p3,p4,p5,p6,p7,p8} for _,point in spairs(self.Points)do table.insert(self.Coords,COORDINATE:NewFromVec3(point)) end return self end function CUBE:GetCenter() local center={x=0,y=0,z=0} for _,point in pairs(self.Points)do center.x=center.x+point.x center.y=center.y+point.y center.z=center.z+point.z end center.x=center.x/8 center.y=center.y/8 center.z=center.z/8 return center end function CUBE:ContainsPoint(point,cube_points) cube_points=cube_points or self.Points local min_x,min_y,min_z=math.huge,math.huge,math.huge local max_x,max_y,max_z=-math.huge,-math.huge,-math.huge for _,p in ipairs(cube_points)do if p.xmax_x then max_x=p.x end if p.y>max_y then max_y=p.y end if p.z>max_z then max_z=p.z end end return point.x>=min_x and point.x<=max_x and point.y>=min_y and point.y<=max_y and point.z>=min_z and point.z<=max_z end LINE={ ClassName="LINE", Points={}, Coords={}, } function LINE:FindOnMap(line_name) local self=BASE:Inherit(self,SHAPE_BASE:FindOnMap(line_name)) for _,layer in pairs(env.mission.drawings.layers)do for _,object in pairs(layer["objects"])do if object["name"]==line_name then if object["primitiveType"]=="Line"then for _,point in UTILS.spairs(object["points"])do local p={x=object["mapX"]+point["x"], y=object["mapY"]+point["y"]} local coord=COORDINATE:NewFromVec2(p) table.insert(self.Points,p) table.insert(self.Coords,coord) end end end end end self:I(#self.Points) if#self.Points==0 then return nil end self.MarkIDs={} return self end function LINE:Find(shape_name) return _DATABASE:FindShape(shape_name) end function LINE:New(...) local self=BASE:Inherit(self,SHAPE_BASE:New()) self.Points={...} self:I(self.Points) for _,point in UTILS.spairs(self.Points)do table.insert(self.Coords,COORDINATE:NewFromVec2(point)) end return self end function LINE:NewFromCircle(center_point,radius,angle_degrees) local self=BASE:Inherit(self,SHAPE_BASE:New()) self.CenterVec2=center_point local angleRadians=math.rad(angle_degrees) local point1={ x=center_point.x+radius*math.cos(angleRadians), y=center_point.y+radius*math.sin(angleRadians) } local point2={ x=center_point.x+radius*math.cos(angleRadians+math.pi), y=center_point.y+radius*math.sin(angleRadians+math.pi) } for _,point in pairs{point1,point2}do table.insert(self.Points,point) table.insert(self.Coords,COORDINATE:NewFromVec2(point)) end return self end function LINE:Coordinates() return self.Coords end function LINE:GetStartCoordinate() return self.Coords[1] end function LINE:GetEndCoordinate() return self.Coords[#self.Coords] end function LINE:GetStartPoint() return self.Points[1] end function LINE:GetEndPoint() return self.Points[#self.Points] end function LINE:GetLength() local total_length=0 for i=1,#self.Points-1 do local x1,y1=self.Points[i]["x"],self.Points[i]["y"] local x2,y2=self.Points[i+1]["x"],self.Points[i+1]["y"] local segment_length=math.sqrt((x2-x1)^2+(y2-y1)^2) total_length=total_length+segment_length end return total_length end function LINE:GetRandomPoint(points) points=points or self.Points local rand=math.random() local random_x=points[1].x+rand*(points[2].x-points[1].x) local random_y=points[1].y+rand*(points[2].y-points[1].y) return{x=random_x,y=random_y} end function LINE:GetHeading(points) points=points or self.Points local angle=math.atan2(points[2].y-points[1].y,points[2].x-points[1].x) angle=math.deg(angle) if angle<0 then angle=angle+360 end return angle end function LINE:GetIndividualParts() local parts={} if#self.Points==2 then parts={self} end for i=1,#self.Points-1 do local p1=self.Points[i] local p2=self.Points[i%#self.Points+1] table.add(parts,LINE:New(p1,p2)) end return parts end function LINE:GetPointsInbetween(amount,start_point,end_point) start_point=start_point or self:GetStartPoint() end_point=end_point or self:GetEndPoint() if amount==0 then return{start_point,end_point}end amount=amount+1 local points={} local difference={x=end_point.x-start_point.x,y=end_point.y-start_point.y} local divided={x=difference.x/amount,y=difference.y/amount} for j=0,amount do local part_pos={x=divided.x*j,y=divided.y*j} local point={x=start_point.x+part_pos.x,y=start_point.y+part_pos.y} table.insert(points,point) end return points end function LINE:GetCoordinatesInBetween(amount,start_point,end_point) local coords={} for _,pt in pairs(self:GetPointsInbetween(amount,start_point,end_point))do table.add(coords,COORDINATE:NewFromVec2(pt)) end return coords end function LINE:GetRandomPoint(start_point,end_point) start_point=start_point or self:GetStartPoint() end_point=end_point or self:GetEndPoint() local fraction=math.random() local difference={x=end_point.x-start_point.x,y=end_point.y-start_point.y} local part_pos={x=difference.x*fraction,y=difference.y*fraction} local random_point={x=start_point.x+part_pos.x,y=start_point.y+part_pos.y} return random_point end function LINE:GetRandomCoordinate(start_point,end_point) start_point=start_point or self:GetStartPoint() end_point=end_point or self:GetEndPoint() return COORDINATE:NewFromVec2(self:GetRandomPoint(start_point,end_point)) end function LINE:GetPointsBetweenAsSineWave(amount,start_point,end_point,frequency,phase,amplitude) amount=amount or 20 start_point=start_point or self:GetStartPoint() end_point=end_point or self:GetEndPoint() frequency=frequency or 1 phase=phase or 0 amplitude=amplitude or 100 local points={} local function sine_wave(x) return amplitude*math.sin(2*math.pi*frequency*(x-start_point.x)+phase) end local x=start_point.x local step=(end_point.x-start_point.x)/20 for _=1,amount do local y=sine_wave(x) x=x+step table.add(points,{x=x,y=y}) end return points end function LINE:GetBoundingBox() local min_x,min_y,max_x,max_y=self.Points[1].x,self.Points[1].y,self.Points[2].x,self.Points[2].y for i=2,#self.Points do local x,y=self.Points[i].x,self.Points[i].y if xmax_x then max_x=x end if y>max_y then max_y=y end end 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 LINE:Draw() for i=1,#self.Coords-1 do local c1=self.Coords[i] local c2=self.Coords[i%#self.Coords+1] table.add(self.MarkIDs,c1:LineToAll(c2)) end end function LINE:RemoveDraw() for _,mark_id in pairs(self.MarkIDs)do UTILS.RemoveMark(mark_id) end end OVAL={ ClassName="OVAL", MajorAxis=nil, MinorAxis=nil, Angle=0, DrawPoly=nil } function OVAL:FindOnMap(shape_name) local self=BASE:Inherit(self,SHAPE_BASE:FindOnMap(shape_name)) for _,layer in pairs(env.mission.drawings.layers)do for _,object in pairs(layer["objects"])do if string.find(object["name"],shape_name,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 return self end function OVAL:Find(shape_name) return _DATABASE:FindShape(shape_name) end function OVAL:New(vec2,major_axis,minor_axis,angle) local self=BASE:Inherit(self,SHAPE_BASE:New()) self.CenterVec2=vec2 self.MajorAxis=major_axis self.MinorAxis=minor_axis self.Angle=angle or 0 return self end function OVAL:GetMajorAxis() return self.MajorAxis end function OVAL:GetMinorAxis() return self.MinorAxis end function OVAL:GetAngle() return self.Angle end function OVAL:SetMajorAxis(value) self.MajorAxis=value end function OVAL:SetMinorAxis(value) self.MinorAxis=value end function OVAL:SetAngle(value) self.Angle=value end function OVAL:ContainsPoint(point) local cos,sin=math.cos,math.sin local dx=point.x-self.CenterVec2.x local dy=point.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 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 OVAL:GetBoundingBox() 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 OVAL:Draw() self.DrawPoly=POLYGON:NewFromPoints(self:PointsOnEdge(20)) self.DrawPoly:Draw(true) end function OVAL:RemoveDraw() self.DrawPoly:RemoveDraw() end function OVAL:PointsOnEdge(num_points) num_points=num_points or 20 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 POLYGON={ ClassName="POLYGON", Points={}, Coords={}, Triangles={}, SurfaceArea=0, TriangleMarkIDs={}, OutlineMarkIDs={}, Angle=nil, Heading=nil } function POLYGON:FindOnMap(shape_name) local self=BASE:Inherit(self,SHAPE_BASE:FindOnMap(shape_name)) for _,layer in pairs(env.mission.drawings.layers)do for _,object in pairs(layer["objects"])do if object["name"]==shape_name then if(object["primitiveType"]=="Line"and object["closed"]==true)or(object["polygonMode"]=="free")then for _,point in UTILS.spairs(object["points"])do local p={x=object["mapX"]+point["x"], y=object["mapY"]+point["y"]} local coord=COORDINATE:NewFromVec2(p) self.Points[#self.Points+1]=p self.Coords[#self.Coords+1]=coord end elseif object["polygonMode"]=="rect"then local angle=object["angle"] local half_width=object["width"]/2 local half_height=object["height"]/2 local p1=UTILS.RotatePointAroundPivot({x=self.CenterVec2.x-half_height,y=self.CenterVec2.y+half_width},self.CenterVec2,angle) local p2=UTILS.RotatePointAroundPivot({x=self.CenterVec2.x+half_height,y=self.CenterVec2.y+half_width},self.CenterVec2,angle) local p3=UTILS.RotatePointAroundPivot({x=self.CenterVec2.x+half_height,y=self.CenterVec2.y-half_width},self.CenterVec2,angle) local p4=UTILS.RotatePointAroundPivot({x=self.CenterVec2.x-half_height,y=self.CenterVec2.y-half_width},self.CenterVec2,angle) self.Points={p1,p2,p3,p4} for _,point in pairs(self.Points)do self.Coords[#self.Coords+1]=COORDINATE:NewFromVec2(point) end elseif object["polygonMode"]=="arrow"then for _,point in UTILS.spairs(object["points"])do local p={x=object["mapX"]+point["x"], y=object["mapY"]+point["y"]} local coord=COORDINATE:NewFromVec2(p) self.Points[#self.Points+1]=p self.Coords[#self.Coords+1]=coord end self.Angle=object["angle"] self.Heading=UTILS.ClampAngle(self.Angle+90) end end end end if#self.Points==0 then return nil end self.CenterVec2=self:GetCentroid() self.Triangles=self:Triangulate() self.SurfaceArea=self:__CalculateSurfaceArea() self.TriangleMarkIDs={} self.OutlineMarkIDs={} return self end function POLYGON:FromZone(zone_name) for _,zone in pairs(env.mission.triggers.zones)do if zone["name"]==zone_name then return POLYGON:New(unpack(zone["verticies"]or{})) end end end function POLYGON:Find(shape_name) return _DATABASE:FindShape(shape_name) end function POLYGON:New(...) local self=BASE:Inherit(self,SHAPE_BASE:New()) self.Points={...} self.Coords={} for _,point in UTILS.spairs(self.Points)do table.insert(self.Coords,COORDINATE:NewFromVec2(point)) end self.Triangles=self:Triangulate() self.SurfaceArea=self:__CalculateSurfaceArea() return self end function POLYGON:GetCentroid() local function sum(t) local total=0 for _,value in pairs(t)do total=total+value end return total end local x_values={} local y_values={} local length=table.length(self.Points) for _,point in pairs(self.Points)do table.insert(x_values,point.x) table.insert(y_values,point.y) end local x=sum(x_values)/length local y=sum(y_values)/length return{ ["x"]=x, ["y"]=y } end function POLYGON:GetCoordinates() return self.Coords end function POLYGON:GetStartCoordinate() return self.Coords[1] end function POLYGON:GetEndCoordinate() return self.Coords[#self.Coords] end function POLYGON:GetStartPoint() return self.Points[1] end function POLYGON:GetEndPoint() return self.Points[#self.Points] end function POLYGON:GetPoints() return self.Points end function POLYGON:GetSurfaceArea() return self.SurfaceArea end function POLYGON:GetBoundingBox() local min_x,min_y,max_x,max_y=self.Points[1].x,self.Points[1].y,self.Points[1].x,self.Points[1].y for i=2,#self.Points do local x,y=self.Points[i].x,self.Points[i].y if xmax_x then max_x=x end if y>max_y then max_y=y end end 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 POLYGON:Triangulate(points) points=points or self.Points 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,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=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 end end end end points=ensure_clockwise(points) divide_recursively(points) return triangles end function POLYGON:CovarianceMatrix() local cx,cy=self:GetCentroid() local covXX,covYY,covXY=0,0,0 for _,p in ipairs(self.points)do covXX=covXX+(p.x-cx)^2 covYY=covYY+(p.y-cy)^2 covXY=covXY+(p.x-cx)*(p.y-cy) end covXX=covXX/(#self.points-1) covYY=covYY/(#self.points-1) covXY=covXY/(#self.points-1) return covXX,covYY,covXY end function POLYGON:Direction() local covXX,covYY,covXY=self:CovarianceMatrix() local theta=0.5*math.atan2(2*covXY,covXX-covYY) return math.cos(theta),math.sin(theta) end function POLYGON: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 POLYGON:GetRandomNonWeightedVec2() return self.Triangles[math.random(1,#self.Triangles)]:GetRandomVec2() end function POLYGON:ContainsPoint(point,polygon_points) local x=point.x local y=point.y polygon_points=polygon_points or self.Points local counter=0 local num_points=#polygon_points for current_index=1,num_points do local next_index=(current_index%num_points)+1 local current_x,current_y=polygon_points[current_index].x,polygon_points[current_index].y local next_x,next_y=polygon_points[next_index].x,polygon_points[next_index].y if((current_y>y)~=(next_y>y))and(x<(next_x-current_x)*(y-current_y)/(next_y-current_y)+current_x)then counter=counter+1 end end return counter%2==1 end function POLYGON:Draw(include_inner_triangles) include_inner_triangles=include_inner_triangles or false for i=1,#self.Coords do local c1=self.Coords[i] local c2=self.Coords[i%#self.Coords+1] table.add(self.OutlineMarkIDs,c1:LineToAll(c2)) end if include_inner_triangles then for _,triangle in ipairs(self.Triangles)do triangle:Draw() end end end function POLYGON:RemoveDraw() for _,triangle in pairs(self.Triangles)do triangle:RemoveDraw() end for _,mark_id in pairs(self.OutlineMarkIDs)do UTILS.RemoveMark(mark_id) end end function POLYGON:__CalculateSurfaceArea() local area=0 for _,triangle in pairs(self.Triangles)do area=area+triangle.SurfaceArea end return area end TRIANGLE={ ClassName="TRIANGLE", Points={}, Coords={}, SurfaceArea=0 } function TRIANGLE:New(p1,p2,p3) local self=BASE:Inherit(self,SHAPE_BASE:New()) self.Points={p1,p2,p3} local center_x=(p1.x+p2.x+p3.x)/3 local center_y=(p1.y+p2.y+p3.y)/3 self.CenterVec2={x=center_x,y=center_y} for _,pt in pairs({p1,p2,p3})do table.add(self.Coords,COORDINATE:NewFromVec2(pt)) end self.SurfaceArea=math.abs((p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y))*0.5 self.MarkIDs={} return self end function TRIANGLE:ContainsPoint(pt,points) points=points or self.Points local function sign(p1,p2,p3) return(p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y) end local d1=sign(pt,self.Points[1],self.Points[2]) local d2=sign(pt,self.Points[2],self.Points[3]) local d3=sign(pt,self.Points[3],self.Points[1]) local has_neg=(d1<0)or(d2<0)or(d3<0) local has_pos=(d1>0)or(d2>0)or(d3>0) return not(has_neg and has_pos) end function 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 TRIANGLE:Draw() for i=1,#self.Coords do local c1=self.Coords[i] local c2=self.Coords[i%#self.Coords+1] table.add(self.MarkIDs,c1:LineToAll(c2)) end end function TRIANGLE:RemoveDraw() for _,mark_id in pairs(self.MarkIDs)do UTILS.RemoveMark(mark_id) 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=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:CountAlive() 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:CountAlive()==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:CountAlive() 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:CountAlive() 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 *** ')